Create and Check a Subject Alternative Name CSR with openssl req
A certificate with just a Common Name breaks the moment it needs a second hostname, and openssl req is how you fix that. Every mainstream TLS client now checks the Subject Alternative Name list instead of trusting the Common Name alone, so this builds and checks a proper SAN-bearing CSR before it goes anywhere near a certificate authority. The examples use OpenSSL 3.0.13 from the installed openssl package. Allow about fifteen minutes if you already know the names the certificate must cover.
The route
Jump straight to the step you need, or tick off Done means at the end.
You need a shell, the openssl package, and a directory where the private key can be protected. This workflow does not need sudo. A CSR is not a certificate and does not make a service trusted by itself.
1. Choose the names and output files
Write down every DNS name the eventual certificate must cover and put them in the Subject Alternative Name (SAN) extension. Relying on the Common Name alone will not fool a modern TLS client; SAN is what it actually checks.
Work in a new directory with restrictive permissions. The private key has no business sitting in a shared temporary directory or a source-control checkout.
$ umask 077
$ mkdir -p ~/private/example-csr
$ cd ~/private/example-csr
Checkpoint
pwd should show the directory where you intend to keep the key and CSR. The private key will be written to api.example.test.key; the request will be written to api.example.test.csr.
2. Generate the key and CSR
One command does the lot: it creates a 2048-bit RSA key, prompts for a passphrase for that key, and adds both DNS names to the request.
$ openssl req -new -newkey rsa:2048 \
-keyout api.example.test.key \
-out api.example.test.csr \
-subj '/C=GB/O=Example Operations/CN=api.example.test' \
-addext 'subjectAltName = DNS:api.example.test,DNS:api.internal.example.test'
OpenSSL asks for a PEM passphrase during key generation. Pick one you have not reused elsewhere and store it in your normal password manager. The CSR is normally shareable; the key never is. If a CA or service specifically insists on an unencrypted key, use -noenc deliberately and lock the file down with filesystem permissions. Do not reach for the deprecated -nodes spelling on OpenSSL 3.
The -subj value uses slash-separated attributes and covers only the subject name. Repeat -addext for further extensions; it takes the same key-value form used in an OpenSSL configuration file. The SAN value above is an extension in the CSR, not a promise that a CA will issue those exact names.
Checkpoint
Both files should exist, and the key should be readable only by you.
$ ls -l api.example.test.key api.example.test.csr
-rw------- 1 you you ... api.example.test.csr
-rw------- 1 you you ... api.example.test.key
Exact sizes and owner names vary. If the command failed, do not send an incomplete CSR to a CA. Read the first error, check the working directory, and rerun with a new output name if either file may have been overwritten.
3. Verify the CSR signature
A CSR is self-signed with the private key corresponding to the public key it contains. Verify that signature before sending it anywhere:
$ openssl req -in api.example.test.csr -noout -verify
Certificate request self-signature verify OK
A clean exit here proves the CSR is internally consistent, nothing more. It does not prove you control either DNS name, and it is no guarantee a CA will approve the request.
Checkpoint
If verification fails, stop. Keep the original key and CSR for investigation, then generate a fresh pair rather than trying to repair the request as text.
4. Inspect the subject and SAN values
Print the fields that matter to a normal TLS request without dumping the entire encoded blob:
$ openssl req -in api.example.test.csr -noout -subject
subject=C=GB, O=Example Operations, CN=api.example.test
$ openssl req -in api.example.test.csr -noout -text \
| grep -A2 -E 'Subject Alternative Name|Public Key Algorithm'
Public Key Algorithm: rsaEncryption
X509v3 Subject Alternative Name:
DNS:api.example.test, DNS:api.internal.example.test
On this OpenSSL build the text output labels the extension X509v3 Subject Alternative Name. Check every character of every name. A typo in a SAN is an issuance or deployment headache, not something TLS will politely correct for you.
No grep on your system, or using a different shell? Drop the pipe and read the full output instead:
$ openssl req -in api.example.test.csr -noout -text
5. Submit only the CSR
Send api.example.test.csr through the CA's documented enrolment process. Never send api.example.test.key. If the CA wants proof you control the SAN names by DNS or HTTP, complete that challenge through the CA itself, not by folding private material into the request.
Inspect any certificate the CA returns separately from the request. A CA can reject an extension, hand back a different validity period, or issue names that do not match what you asked for. Compare the issued certificate's SAN extension against the request before installing it.
6. Use a configuration file when the request is repeatable
Command-line -subj and -addext suit a one-off request. For anything repeatable, put the distinguished name and extensions in a file that gets reviewed and kept with your process documentation.
[ req ]
prompt = no
distinguished_name = request_dn
req_extensions = request_extensions
[ request_dn ]
C = GB
O = Example Operations
CN = api.example.test
[ request_extensions ]
subjectAltName = DNS:api.example.test,DNS:api.internal.example.test
Save it as request.cnf, then generate the request with:
$ openssl req -new -newkey rsa:2048 \
-config request.cnf \
-keyout api.example.test.key \
-out api.example.test.csr
By default, openssl req reads the req section. Use -section NAME when the file has another request section. Keep private-key passwords out of the configuration file entirely; if you must automate a password, use the documented passphrase-source options for your deployment rather than dropping a secret into a command line or a checked-in file.
Common traps and recovery
- The command asks questions unexpectedly: add
-subj, setprompt = noin the configuration, or add-batchfor a deliberately non-interactive run. Never answer prompts with guessed values. - The SAN is missing: inspect with
-text. A Common Name does not substitute for SAN. Add-addextor areq_extensionssection, then regenerate the CSR. - The key was printed on screen: this happens when a new key is generated without
-keyout. Treat the displayed key as burnt, do not use it, and generate a replacement with an explicit private output path. - You need to undo a test request: delete the CSR and key only after confirming no CA application or service still needs them. Deleting the key is irreversible for that key pair and can make an issued certificate unusable.
Done means
- The CSR contains the intended subject and every required SAN.
openssl req -noout -verifyreports that the self-signature verifies.- The private key is encrypted or has an explicitly chosen, restrictive protection model.
- Only the CSR was submitted to the CA; the private key remains private.
- The issued certificate, when it comes back, has been inspected rather than assumed to match the request.