<!-- https://pkizerotohero.com/guides/create-csr-openssl/ | Markdown mirror of the HTML page | PKI Zero to Hero, by Duyutech BV -->

> Create a private key and a CSR with OpenSSL, with Subject Alternative Names on one line or in a config file, then verify the request before you send it.

Source: https://pkizerotohero.com/guides/create-csr-openssl/ · Last reviewed: 2026-10-20

How-to guide · Create keys and CSRs

# How to create a key and a CSR with OpenSSL (with SANs)

**In one sentence:**

You will have a new private key that never leaves your server, and a verified certificate signing request that lists every name your certificate needs.

Task

[Create keys and CSRs](https://pkizerotohero.com/guides/#task-create)

Platforms

- Linux
- macOS
- Windows

Who it is for

- [Developer](https://pkizerotohero.com/start/#developer)
- [IT ops](https://pkizerotohero.com/start/#itops)
- [Beginner](https://pkizerotohero.com/start/#beginner)

Level

Beginner · 6 steps

## Before you start

- **OpenSSL 1.1.1 or later** for the one-line method (`-addext`). Older versions need the configuration file in step 4.
- **Every name the certificate must cover**: for example `shop.example.com` and `www.shop.example.com`. Ask the people who run the DNS and the load balancer.
- **The machine that will use the certificate**, or a secure one close to it. The key is created here and should stay here.

A [certificate signing request](https://pkizerotohero.com/handbook/glossary/#certificate-signing-request) (CSR) is what you send to a CA: your public key and the names you want, signed with your private key. The CA never needs your private key. If anyone asks you for it, the answer is no.

## Steps

### 1. Choose a key type

- **ECDSA P-256**: small, fast and supported by every modern client. A good default.
- **RSA 3072** (or 2048): works with very old clients and appliances.

Public CAs accept RSA keys of at least 2048 bits and ECDSA keys on the P-256, P-384 and P-521 curves ([Baseline Requirements, section 6.1.5](https://cabforum.org/working-groups/server/baseline-requirements/requirements/)). Generate a fresh key for every new certificate, including renewals.

### 2. Create the private key

Create the key in its own step, so that its file permissions are right from the start:

```bash
# ECDSA P-256
(umask 077; openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -out shop.key)

# or RSA 3072
(umask 077; openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:3072 -out shop.key)
```

```bash
ls -l shop.key
head -1 shop.key
```

```text
-rw------- 1 user user 2484 Oct 20 04:07 shop.key
-----BEGIN PRIVATE KEY-----
```

`umask 077` makes the file readable only by you (on Linux and macOS; Windows file permissions work differently). The key is not encrypted, which servers usually need to start unattended. To protect it with a pass phrase instead, add `-aes-256-cbc`; then every service start needs that pass phrase. On OpenSSL 3, `-quiet` hides the progress dots that RSA key generation prints.

### 3. Create the CSR with its names on one line

```bash
openssl req -new -key shop.key \
  -subj "/C=BE/O=Example Shop/CN=shop.example.com" \
  -addext "subjectAltName=DNS:shop.example.com,DNS:*.shop.example.com,IP:192.0.2.10" \
  -out shop.csr
```

- `-subj` sets the subject. Put your main name in `CN`, but know that clients ignore it for host names.
- `-addext "subjectAltName=…"` sets the [Subject Alternative Name](https://pkizerotohero.com/handbook/glossary/#subject-alternative-name) list, which is what clients check. List every name, including the one in `CN`.
- A [wildcard](https://pkizerotohero.com/handbook/glossary/#wildcard-certificate) such as `*.shop.example.com` covers exactly one label (`api.shop.example.com`), never deeper names and never `shop.example.com` itself. Add that name separately.
- `IP:` adds an IP address. Public CAs issue those only for public addresses; internal CAs can use them for private ones.

Warning

Git Bash on Windows rewrites -subj

Git Bash turns an argument that starts with `/` into a Windows path, and OpenSSL then fails with: `req: subject name is expected to be in the format /type0=value0/type1=value1/type2=... where characters may be escaped by \. This name is not in that format: 'C:/Program Files/Git/C=BE/O=Example Shop/CN=shop.example.com'`. Put `MSYS_NO_PATHCONV=1` in front of the command, or use the configuration file in step 4.

### 4. Or describe many names in a configuration file

For more than a few names, a file is easier to review and to keep in version control. It contains no secrets. Save it as `req.cnf`:

```text
[req]
prompt             = no
distinguished_name = dn
req_extensions     = ext

[dn]
C  = BE
O  = Example Shop
CN = shop.example.com

[ext]
subjectAltName = @alt

[alt]
DNS.1 = shop.example.com
DNS.2 = www.shop.example.com
DNS.3 = api.example.com
IP.1  = 192.0.2.10
```

```bash
openssl req -new -key shop.key -config req.cnf -out shop.csr
```

This also works on OpenSSL 1.0.2, which has no `-addext`. Use the file or `-addext` for the names, not both: OpenSSL 3 refuses a second SAN list with `Error adding extensions defined via -addext` (OpenSSL 3.2: `Error adding request extensions defined via -addext`) and `duplicate attribute`.

### 5. Read the CSR back before you send it

```bash
openssl req -in shop.csr -noout -text -verify
```

```text
Certificate request self-signature verify OK
Certificate Request:
    Data:
        Version: 1 (0x0)
        Subject: C=BE, O=Example Shop, CN=shop.example.com
        Subject Public Key Info:
            Public Key Algorithm: id-ecPublicKey
                Public-Key: (256 bit)
                ...
                ASN1 OID: prime256v1
                NIST CURVE: P-256
        Attributes:
            Requested Extensions:
                X509v3 Subject Alternative Name: 
                    DNS:shop.example.com, DNS:*.shop.example.com, IP Address:192.0.2.10
    Signature Algorithm: ecdsa-with-SHA256
```

Check three things: `self-signature verify OK`, every name under **Requested Extensions**, and the key type and size you chose. The file starts with `-----BEGIN CERTIFICATE REQUEST-----`.

### 6. Send the CSR, keep the key

Send `shop.csr` to the CA: paste it into their web form, upload it, or hand it to your internal PKI team. A CSR is public information. Keep `shop.key` on the server, back it up the way your organisation backs up secrets, and never email it.

When the certificate comes back:

1. Check that it matches your key: [How to check that a private key matches a certificate](https://pkizerotohero.com/guides/check-key-matches-certificate/).
2. Check that the CA kept all your names: [How to inspect a certificate with OpenSSL](https://pkizerotohero.com/guides/inspect-certificate-openssl/).
3. Build the file your server needs: [How to build the full chain file](https://pkizerotohero.com/guides/build-fullchain/), or a PFX for Windows with [How to convert PEM to PFX](https://pkizerotohero.com/guides/convert-pfx-pem/).

## Check it worked

Paste the CSR (never the key) into the CSR decoder. It runs in your browser and shows the subject, the requested names and whether the self-signature verifies:

*Interactive tool: CSR decoder (runs in the browser at [/tools/csr/](https://pkizerotohero.com/tools/csr/))*

Then confirm that the CSR carries the public key of the key you will keep. These two values must be equal:

```bash
openssl pkey -in shop.key -pubout -outform DER | openssl sha256
openssl req -in shop.csr -noout -pubkey | openssl pkey -pubin -outform DER | openssl sha256
```

## If something goes wrong

- **`subject name is expected to be in the format …`** in Git Bash: see the warning in step 3.
- **OpenSSL rejects `-addext` as an unknown option:** your OpenSSL is older than 1.1.1. Use the configuration file in step 4, or a newer OpenSSL.
- **`Error adding extensions defined via -addext` (or `Error adding request extensions …`) … `duplicate attribute`**: the configuration file already sets `subjectAltName`. Set the names in one place only.
- **The CA rejects the CSR:** the key may be too small or of a type the CA does not accept, or a name fails the CA’s checks. Read the CA’s message and step 1 again.
- **The certificate arrives without one of your names**, and browsers show [`NET::ERR_CERT_COMMON_NAME_INVALID`](https://pkizerotohero.com/errors/chrome-err-cert-common-name-invalid/) for it: the name was missing from the SAN list, or the CA could not validate it. Fix the CSR and request again.
- **Java says [`No subject alternative names matching IP address … found`](https://pkizerotohero.com/errors/java-no-subject-alternative-names/)**: clients connect by IP address, so the IP must be in the SAN list as `IP:`, not `DNS:`.

## Why this works

The CSR format is PKCS #10, RFC 2986 (PKCS #10). It packs your subject, your public key and the extensions you request into one structure, then signs it with your private key. That signature proves to the CA that you hold the key, without revealing it. The CA validates the names, copies what it accepts into a certificate and signs that with its own key. [Asking for a certificate](https://pkizerotohero.com/handbook/csr/) explains the whole exchange, [Names and jobs](https://pkizerotohero.com/handbook/names-and-key-usage/) explains SANs and wildcards, and [Key pairs](https://pkizerotohero.com/handbook/key-pairs/) explains why the private key never needs to travel.

## Go deeper

- [OpenSSL `req` manual](https://docs.openssl.org/master/man1/openssl-req/): `-subj`, `-addext`, `-config` and the configuration file format.
- [OpenSSL `genpkey` manual](https://docs.openssl.org/master/man1/openssl-genpkey/): key types and options.
- [OpenSSL `x509v3_config`](https://docs.openssl.org/master/man5/x509v3_config/): the syntax for `subjectAltName` and other extensions.
- [RFC 2986](https://www.rfc-editor.org/rfc/rfc2986): the PKCS #10 certificate request syntax.
- [RFC 9525](https://www.rfc-editor.org/rfc/rfc9525): how clients match host names against the SAN.
- [CA/Browser Forum Baseline Requirements](https://cabforum.org/working-groups/server/baseline-requirements/requirements/): section 6.1.5 on key sizes for public certificates.

**Early edition.** Checked against the standards; an independent expert review is in progress. Found a mistake? [Tell us](https://pkizerotohero.com/contact/?topic=content-error&page=/guides/create-csr-openssl/).

Last reviewed 20 October 2026Edition 0.9

## Keep going

### Learn why it works

- [Chapter 10: Asking for a certificate: key generation and the CSR](https://pkizerotohero.com/handbook/csr/)
- [Chapter 9: Names and jobs: SANs, wildcards, key usage and the CA flag](https://pkizerotohero.com/handbook/names-and-key-usage/)
- [Chapter 3: Key pairs: public and private keys](https://pkizerotohero.com/handbook/key-pairs/)

### Error pages

- [NET::ERR\_CERT\_COMMON\_NAME\_INVALID](https://pkizerotohero.com/errors/chrome-err-cert-common-name-invalid/)
- [No subject alternative names matching IP address … found](https://pkizerotohero.com/errors/java-no-subject-alternative-names/)

### Tools

- [CSR decoder](https://pkizerotohero.com/tools/csr/): Check a certificate signing request before you send it to a CA.

### Topics

- [Certificates](https://pkizerotohero.com/topics/certificates/)

## More guides: Create keys and CSRs

- [How to create a CSR on Windows (certreq and IIS)](https://pkizerotohero.com/guides/create-csr-windows/)
- [How to create a CSR with Java keytool](https://pkizerotohero.com/guides/create-csr-java-keytool/)
