# Generate a CSR with Subject Alternative Names

To generate a CSR with Subject Alternative Names, run `openssl req -new -newkey rsa:2048 -nodes -keyout example.com.key -out example.com.csr -subj "/CN=example.com" -addext "subjectAltName=DNS:example.com,DNS:www.example.com"`. The `-addext` flag requires OpenSSL 1.1.1 or later; on older versions you must supply the extension through a config file instead. Every hostname the certificate needs to cover must appear in the SAN list, including the one already in the Common Name, because browsers have ignored the Common Name for hostname matching since Chrome 58.

Source: https://certguru.io/openssl/generate-csr-with-san/
Last updated: 2026-08-19
Commands verified against OpenSSL 3.5.7.

---

## The command

*Generate a 2048-bit key and a CSR covering two hostnames*

```bash
openssl req -new \
  -newkey rsa:2048 -nodes -keyout example.com.key \
  -out example.com.csr \
  -subj "/C=US/ST=California/L=San Francisco/O=Example Inc/CN=example.com" \
  -addext "subjectAltName=DNS:example.com,DNS:www.example.com"
```

This writes two files: an unencrypted private key and the CSR you send to the CA.

| Placeholder | What to put there |
| --- | --- |
| `example.com.key` | The new private key. Never send this to anyone, including the CA. |
| `example.com.csr` | The certificate signing request. This is the file you submit. |
| `-nodes` | Leaves the private key unencrypted. Required if a web server must start unattended. |
| `/C=..., /ST=..., /O=...` | Country, state and organisation. Public CAs verify these for OV and EV certificates. |
| `/CN=example.com` | Common Name. Keep it as the primary hostname, but the SAN list is what actually matters. |

> **-nodes leaves the key unencrypted**
>
> A key written with `-nodes` is readable by anyone who can read the file. That is normally what you
>   want for a web server, which cannot prompt for a passphrase at boot — but it means file permissions
>   are now the only thing protecting it. Set them tightly: `chmod 600 example.com.key`.

## Adding more hostnames

`subjectAltName` takes a comma-separated list. There is no practical limit for an internal CA, though public CAs cap it (typically at 100 names):

```bash
openssl req -new -newkey rsa:2048 -nodes \
  -keyout example.com.key -out example.com.csr \
  -subj "/CN=example.com" \
  -addext "subjectAltName=DNS:example.com,DNS:www.example.com,DNS:api.example.com,DNS:*.staging.example.com"
```

A wildcard such as `*.staging.example.com` matches exactly one label. It covers `a.staging.example.com` but **not** `staging.example.com` itself and **not** `a.b.staging.example.com`, so list the apex separately when you need both.

## Using an EC key instead

ECDSA keys are smaller and faster than RSA at equivalent strength, and P-256 is universally supported:

*Same CSR with a P-256 EC key*

```bash
openssl req -new \
  -newkey ec -pkeyopt ec_paramgen_curve:prime256v1 -nodes \
  -keyout example.com.key -out example.com.csr \
  -subj "/CN=example.com" \
  -addext "subjectAltName=DNS:example.com,DNS:www.example.com"
```

## On OpenSSL 1.0.2 — the config file method

`-addext` does not exist before OpenSSL 1.1.1. Older versions need the extension supplied through a config file. This method also works on every newer version, so it is the portable choice for scripts you cannot pin:

*csr.conf*

```ini
[ req ]
default_bits       = 2048
prompt             = no
distinguished_name = dn
req_extensions     = req_ext

[ dn ]
C  = US
ST = California
L  = San Francisco
O  = Example Inc
CN = example.com

[ req_ext ]
subjectAltName = @alt_names

[ alt_names ]
DNS.1 = example.com
DNS.2 = www.example.com
DNS.3 = api.example.com
```

```bash
openssl req -new -newkey rsa:2048 -nodes \
  -keyout example.com.key -out example.com.csr \
  -config csr.conf
```

> **req_extensions, not x509_extensions**
>
> In a CSR config the section must be referenced by `req_extensions`. `x509_extensions` is read only
>   when you are producing a self-signed certificate with `-x509`. Getting these two confused is the
>   usual reason a config-file CSR comes out with no SANs at all.

## Always verify before you submit

A CSR with missing SANs produces a certificate that fails in browsers, and you will not find out until after issuance. Check first:

*Read back the CSR you just generated*

```bash
openssl req -in example.com.csr -noout -text
```

Expected output:

```
Certificate Request:
    Data:
        Version: 1 (0x0)
        Subject: C = US, ST = California, L = San Francisco, O = Example Inc, CN = example.com
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                Public-Key: (2048 bit)
        Attributes:
        Requested Extensions:
            X509v3 Subject Alternative Name:
                DNS:example.com, DNS:www.example.com
    Signature Algorithm: sha256WithRSAEncryption
```

For a one-line check in CI:

```bash
openssl req -in example.com.csr -noout -text | grep -A1 "Subject Alternative Name"
```

If that prints nothing, the extension is absent — go back and check your OpenSSL version or your config section names.

## Confirm the key matches

The CSR and the key are only useful together. These two commands must print the same hash:

```bash
openssl req -in example.com.csr -noout -pubkey | openssl sha256
openssl pkey -in example.com.key -pubout | openssl sha256
```

> **Why compare public keys rather than moduli**
>
> The older trick of comparing `-modulus` output works for RSA only and prints nothing useful for an
>   EC key. Comparing the DER-encoded public key works for every algorithm, so it is the check worth
>   memorising.


## Frequently asked questions

### Do I still need to set the Common Name?

Set it, but do not rely on it. Every current browser matches hostnames against the SAN extension only and ignores the Common Name entirely, a change Chrome made in version 58. Most public CAs still require a CN and will copy it into the SAN list, but you should list it explicitly rather than depend on that.

### Why is my -addext argument being ignored?

`-addext` was added in OpenSSL 1.1.1. On 1.0.2 the flag is silently unrecognised in some builds and produces a CSR with no extensions at all. Run `openssl version` and use the config-file method below if you are on anything older.

### Can I put an IP address in a SAN?

Yes, with the `IP:` prefix, as in `-addext "subjectAltName=DNS:example.com,IP:192.0.2.10"`. Public CAs will not issue for private IP ranges, so in practice this is only useful for an internal CA.

### Does the CA have to honour my SAN list?

No. The CSR is a request. The CA decides what goes into the issued certificate, and it will drop any hostname you have not proven control of. Always inspect the issued certificate rather than assuming it matches the CSR.


## References

- [openssl-req(1)](https://docs.openssl.org/3.5/man1/openssl-req/) — -addext, which is how a SAN reaches the request
- [RFC 6125: service identity in TLS](https://www.rfc-editor.org/rfc/rfc6125) — why the common name stopped being authoritative for hostnames

## Related pages

- https://certguru.io/openssl/generate-csr/
- https://certguru.io/openssl/view-csr/
- https://certguru.io/errors/err-cert-common-name-invalid/
- https://certguru.io/concepts/wildcard-certificate/
- https://certguru.io/concepts/san/
- https://certguru.io/concepts/csr/
- https://certguru.io/convert/pem-to-pfx/

--
CertGuru — https://certguru.io
