Certificate signing requests explained: what a CSR contains and how to make one correctly

A certificate signing request is the one file in the TLS workflow you must get right before anyone else is involved: the certificate authority copies its contents into the certificate, so a hostname missing from the request is missing from what you pay for. This guide explains what PKCS#10 encodes, which subject fields a public CA still reads, why hostnames belong in the Subject Alternative Name extension and how to choose a key, then shows how to generate, decode, match and de-passphrase a request.

What a CSR is — and what it is not

A CSR is a PKCS#10 CertificationRequest (RFC 2986) with three parts. CertificationRequestInfo holds a version (always 0; OpenSSL prints Version: 1 (0x0)), the subject distinguished name, a SubjectPublicKeyInfo block with your public key, and attributes — the one that matters being extensionRequest (OID 1.2.840.113549.1.9.14), which wraps the X.509 extensions you want in the certificate, above all subjectAltName (OID 2.5.29.17). The second part names the signature algorithm, such as sha256WithRSAEncryption or ecdsa-with-SHA256. The third is a signature over the info block made with the private key that pairs with the public key inside — the proof of possession the CA verifies before issuing, so nobody can obtain a certificate for a key they do not hold.

Base64-armoured, the file begins -----BEGIN CERTIFICATE REQUEST----- (RFC 7468). Windows certreq, IIS and Java keytool -certreq write -----BEGIN NEW CERTIFICATE REQUEST----- around identical bytes; a portal that rejects the NEW spelling is fixed by editing those two lines. What the file does notcontain is your private key. It is public from end to end, and it cannot terminate a TLS connection, because nothing in it has been vouched for yet: a certificate is a CSR’s contents rewritten and signed by a CA.

The subject fields, and which ones still matter

OpenSSL prompts for the subject in the order C, ST, L, O, OU, CN and emailAddress, and prints it as Subject: C = IN, ST = Maharashtra, L = Mumbai, O = Example Ltd, CN = example.com. What survives depends on the validation level, and above DV the CA writes the values it verified, not the values you typed.

FieldOIDDomain-validated (DV)Organisation / extended validation (OV, EV)Notes
CN — Common Name2.5.4.3Optional; if present it must repeat a SANSameOne value, 64 characters maximum. Not used for hostname matching.
O — Organization2.5.4.10DroppedThe verified legal nameType the registered name, not the brand.
OU — Organizational Unit2.5.4.11ProhibitedProhibitedCA/Browser Forum ballot SC47v2: banned from certificates issued on or after 1 September 2022.
L, ST, C — city, state, country2.5.4.7, 2.5.4.8, 2.5.4.6DroppedVerified against company registrationC is an ISO 3166-1 two-letter code (IN, US, DE), never a country name.
emailAddress1.2.840.113549.1.9.1DroppedDroppedBelongs in S/MIME certificates, not server certificates.

For a DV certificate — what Let’s Encrypt issues — the CA reads only the SAN list, checks that you control each name and discards the rest. That is why the CSR generator marks every field except the Common Name optional.

Why hostnames go in the SAN extension, not the CN

RFC 2818 deprecated the Common Name as the place for a server’s hostname in 2000, and RFC 6125 (2011) made the rule explicit: a client that sees a subjectAltName extension must use it and not fall back to the CN. Browsers removed the fallback — Chrome 58 in April 2017, Apple in iOS 13 and macOS 10.15 for certificates issued after 1 July 2019 — so a certificate whose only hostname sits in the CN fails in Chrome with NET::ERR_CERT_COMMON_NAME_INVALID, and the Baseline Requirements make the SAN extension mandatory. A CSR with no SAN extension leaves the CA to guess — some copy the CN into a SAN, others reject — so the generator always writes the CN as the first SAN and appends whatever else you list, deduplicated.

Wildcards and IP addresses

A wildcard SAN *.example.com matches exactly one label (RFC 6125): it covers www.example.com but not example.com and not a.b.example.com, so a certificate for the bare domain needs both example.com and *.example.com — the most common omission we see in decoded requests. The wildcard may only occupy the leftmost label, and public CAs refuse it on a public suffix such as *.co.uk. An IP address is a different SAN type, iPAddress (GeneralName tag 7); the generator encodes a dotted IPv4 address such as 203.0.113.7 that way, and the decoder prints it with an IP prefix. Public CAs issue only for publicly routed addresses — reserved ranges and internal names such as server.local have been banned since 2015, which is what self-signed certificates are for.

Choosing the key: RSA or ECDSA

The key pair is fixed at CSR time; changing it means a new request. Public CAs accept RSA, with 2048 bits as the Baseline Requirements floor, and ECDSA on P-256, P-384 and P-521.

KeySecurity strength (NIST SP 800-57)Public key as encoded in the certificateSignature sizeUse it when
RSA 2048112-bit294 bytes256 bytesAnything old must connect — Windows XP, Java 6, appliances.
RSA 3072128-bit422 bytes384 bytesA policy wants more than 112 bits but the stack must stay RSA.
RSA 4096≈ 140-bit550 bytes512 bytesRarely worth it for TLS: slower handshakes, no CA requires it.
ECDSA P-256128-bit91 bytes≈ 72 bytesThe modern default: stronger than RSA 2048, a third of the bytes, far faster to sign with.
ECDSA P-384192-bit120 bytes≈ 104 bytesGovernment or CNSA-style requirements.

Where the key is generated matters as much as its size: the CSR ceremony exists so a CA can vouch for a key it has never seen, and a key minted on someone else’s server has been seen. Generate it on the machine that will serve with it, or one you control. The generator here calls the browser’s WebCrypto crypto.subtle.generateKey in your tab and never transmits the result — the only acceptable arrangement for a web-based generator. It offers RSA 2048, 3072 and 4096 (exponent 65537, signed with SHA-256) and ECDSA P-256 (SHA-256) and P-384 (SHA-384), and returns the key as unencrypted PKCS#8, a -----BEGIN PRIVATE KEY----- block.

Generating the request

With OpenSSL 1.1.1 or 3.x one command makes both key and CSR; -addext puts the names into the extension request, and -nodes (-noenc in OpenSSL 3) leaves the key unencrypted:

# RSA 2048
openssl req -new -newkey rsa:2048 -nodes \
  -keyout example.com.key -out example.com.csr \
  -subj "/C=IN/ST=Maharashtra/L=Mumbai/O=Example Ltd/CN=example.com" \
  -addext "subjectAltName=DNS:example.com,DNS:www.example.com"

# ECDSA P-256
openssl req -new -newkey ec -pkeyopt ec_paramgen_curve:P-256 -nodes \
  -keyout example.com.key -out example.com.csr \
  -subj "/CN=example.com" \
  -addext "subjectAltName=DNS:example.com,DNS:*.example.com"

The browser generator does the same with a form. The Common Name is the only required field — leave it empty and it stops with Enter the Common Name — the domain the certificate is for, e.g. example.com.; type a country name in C and it stops with Country must be the two-letter ISO code, e.g. IN, US, DE. Extra names go in one box, separated by commas, spaces or semicolons. Generate, then download two files named after the CN: example.com.csr for the CA and example.com.key for the server (a wildcard CN becomes wildcard.example.com.csr). Keep the .key file: the certificate is useless without it and no CA can regenerate it.

Reading a CSR before you send it

openssl req -in example.com.csr -noout -text -verify prints the structure and checks the self-signature; the lines that matter:

Certificate request self-signature verify OK     # OpenSSL 3; 1.1.1 prints "verify OK"
Certificate Request:
    Data:
        Version: 1 (0x0)
        Subject: C = IN, ST = Maharashtra, L = Mumbai, O = Example Ltd, 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, IP Address:203.0.113.7
    Signature Algorithm: sha256WithRSAEncryption

The CSR decoder shows the same facts without a terminal. Paste the block or drop the .csr file — it accepts PEM in the plain or NEW spelling and a binary DER request (first byte 0x30). It reports the Common Name, the full subject, the SAN list as Requests names: …, the key as RSA 2048 or ECDSA P-256, the signature as RSASSA-PKCS1-v1_5 / SHA-256 or similar, and the verdict signature verifies — CSR is intact or signature does NOT verify — the CSR is corrupted or was edited. A failing signature nearly always means a truncated paste — a lost last line of Base64. Omit the header lines and it says No "BEGIN CERTIFICATE REQUEST" block found — paste the whole CSR including the BEGIN/END lines.

Five things to check:

  1. Every hostname is in the SAN list — apex and www, or apex plus wildcard. No Subject Alternative Names — most CAs will only issue for names listed as SANs. means regenerate.
  2. The key is RSA 2048 or larger, or ECDSA P-256/P-384; RSA 1024 is refused outright.
  3. The signature is SHA-256 or better. sha1WithRSAEncryption means an old OpenSSL or a default_md = sha1 line in openssl.cnf; 1.1.0 and later default to SHA-256.
  4. No OU, and C is two letters.
  5. The signature verifies.

Does the private key match?

CSR, private key and the certificate the CA returns all carry or imply one public key, and a mismatch turns a renewal into an outage: Nginx refuses to start with key values mismatch. The textbook check compares RSA moduli; the -pubkey form works for ECDSA too:

# RSA only
openssl x509 -noout -modulus -in example.com.crt | openssl sha256
openssl rsa  -noout -modulus -in example.com.key | openssl sha256
openssl req  -noout -modulus -in example.com.csr | openssl sha256

# any key type: compare the SubjectPublicKeyInfo
openssl x509 -pubkey -noout -in example.com.crt | openssl sha256
openssl pkey -pubout       -in example.com.key | openssl sha256
openssl req  -pubkey -noout -in example.com.csr | openssl sha256

The certificate ↔ key matcher is the second form in a page. Three boxes — certificate, private key, CSR — and any two will do (Provide at least two of: certificate, private key, CSR.); dropped files land in the right box by their BEGIN line, and a binary .pfx or .der is refused with a note to convert it to PEM first. It reads PKCS#8 keys (plain or encrypted, given the passphrase), traditional RSA PRIVATE KEY blocks including the legacy Proc-Type: 4,ENCRYPTED form, and unencrypted SEC1 EC PRIVATE KEY blocks on P-256, P-384 or P-521, derives the SubjectPublicKeyInfo from each and compares the bytes pairwise. The three-way result is the diagnosis: certificate ↔ CSR match but key mismatch means you hold an older .key from a previous order; CSR ↔ key match but certificate mismatch means the CA issued from a different request.

Passphrase-protected keys

A private key can be stored encrypted: modern tools write PKCS#8 -----BEGIN ENCRYPTED PRIVATE KEY----- (PBES2, usually AES-256-CBC); older ones a -----BEGIN RSA PRIVATE KEY----- block with Proc-Type: 4,ENCRYPTED and DEK-Info: AES-256-CBC,… headers. That is right for a backup and wrong for a server that must load the key unattended: Nginx prompts Enter PEM pass phrase: on a console, and under systemd, with no console, fails with PEM_read_bio_PrivateKey() failed and a bad password read (OpenSSL 1.1) or bad decrypt (OpenSSL 3) reason. ssl_password_file (Nginx 1.7.3+) and Apache’s SSLPassPhraseDialog exec: merely move the secret to another file on the same disk. The norm is a plain key protected by chmod 600, plus an encrypted copy for backups.

openssl pkey -in encrypted.key -out plain.key strips a passphrase; openssl pkey -in plain.key -aes-256-cbc -out encrypted.key adds one. The key passphrase tool reads the same formats as the matcher: Remove passphrase writes unencrypted PKCS#8 as private-key.pem, Set / change passphrase writes AES-256 encrypted PKCS#8 as private-key-encrypted.pem. A wrong passphrase on a PKCS#8 key stops with Could not decrypt this key — check the passphrase.; on a legacy RSA key with Could not read the private key — if it is encrypted, enter its passphrase. The one form neither tool opens is an encrypted SEC1 EC key; both stop with Encrypted SEC1 EC keys are not supported — decrypt it first: openssl ec -in key.pem -out key-plain.pem.

Common mistakes

  • Letting a portal generate the key. If the private key is shown to you afterwards, it was made on their server; if it never is, the certificate cannot move. Paste only a CSR you made yourself.
  • Requesting the wrong names. A wildcard that does not cover the apex, a forgotten www, last year’s CSR reused after a hostname was added. The decoder’s Requests names: line is exactly what the CA will issue for.
  • Reusing the key at every renewal. One key then protects the domain for years. certbot generates a new key on every renewal unless you pass --reuse-key, and with public certificate lifetimes capped at 200 days since 15 March 2026 (100 days from March 2027, ballot SC-081) rotation has to be automated anyway; keep a key only when something is pinned to it, such as a DANE TLSA 3 1 1 record.
  • Filling in OU. Since 1 September 2022 CAs strip it or reject the request, rarely saying why; tutorials that print OU=IT in their example subject are the usual source.
  • A SHA-1 signed CSR. The hash never carries into the certificate, but several CAs refuse sha1WithRSAEncryption requests. Regenerate with a current OpenSSL, or add -sha256.
  • Completing the request on a different machine.On Windows the pending request and its key live in the certificate store of the machine that made the CSR; IIS’s Complete Certificate Request elsewhere fails with Cannot find the certificate request that is associated with this certificate file. Complete it where you started, then export a PFX — see the PFX guide.

Do this

  • Generate the key where it will be used, RSA 2048 or ECDSA P-256.
  • List every hostname as a SAN (apex and www, or apex plus wildcard); fill in O, L, ST and C only for OV or EV; leave OU empty.
  • Decode the CSR before submitting it: SAN list, key size, SHA-256 signature, self-signature verifies.
  • When the certificate arrives, run certificate, key and CSR through the matcher before touching the server.
  • Serve from a passphrase-less key with chmod 600; keep an AES-256 encrypted copy for backups; rotate the key at each renewal.

Frequently asked questions

Does a CSR contain the private key?

No. A CSR holds the public key, the subject fields, the requested extensions and a signature made with the private key — the key itself is never inside it. That is why you can paste a CSR into a web form safely, and why the private key must be kept from the moment the CSR is made.

Can I reuse an old CSR to renew a certificate?

Most CAs accept it if the names have not changed, but reusing the CSR means reusing the private key. Unless something depends on the key staying the same (a DANE TLSA record, for example), generate a fresh key and CSR at each renewal.

What key size should a CSR use in 2026?

RSA 2048 if anything old talks to the server, ECDSA P-256 otherwise. Both are accepted by every public CA; the Baseline Requirements set RSA 2048 as the minimum and allow P-256, P-384 and P-521. RSA 4096 mainly costs handshake time and buys little.

Why does Chrome say NET::ERR_CERT_COMMON_NAME_INVALID when the Common Name is right?

Because Chrome has ignored the Common Name since version 58 (April 2017) and checks the hostname only against the Subject Alternative Name extension. The certificate has no SAN, or the SAN list is missing the name you typed. Regenerate the CSR with every hostname listed as a SAN.

Can I edit a CSR to add a domain?

No. The CSR is signed over its own contents, so any edit breaks the self-signature and the CA will reject it. Generate a new CSR — with the same key if you must, but usually with a new one — that lists all the names.

Tools used in this guide

Every one of these runs in your browser — the files you work on never leave your device.

More certificates & keys guides