Set X.509v3 Extensions with an OpenSSL Config File
A certificate comes back missing its subjectAltName, and that gap almost always traces to the x509v3_config extension sections. Every modern browser rejects a certificate like that. This guide builds a small config file, uses it to issue a test certificate, and inspects the resulting extensions. The same pattern works for a certificate signing request (CSR) and for a CA signing operation.
The route
Jump straight to the step you need, or tick off Done means at the end.
- Time: about 15 minutes.
- You need: OpenSSL 3.x, a shell, and permission to write temporary files. This guide uses OpenSSL 3.6.4 as installed here.
The private key and certificate in the examples are test material only; do not use them for a real service.
1. Decide what the certificate is for
Write down the certificate's role before writing its configuration.
- End-entity TLS certificate: normally
CA:false, a key usage suitable for its key, an extended key usage such asserverAuth, and every DNS name or IP address that clients will use insubjectAltName. - CA certificate: must have
CA:true.
These are certificate constraints, not comments for a human operator. Do not put a real private key in a shared configuration file, and do not assume a certificate's common name replaces a subject alternative name: modern TLS clients check the SAN extension for the identity.
2. Create the extension sections
Save this as /tmp/x509v3-guide.cnf. The req section tells openssl req where the distinguished name and extension sections are. x509_extensions applies when -x509 creates a certificate. req_extensions applies when the command creates a CSR.
[ req ]
distinguished_name = dn
prompt = no
x509_extensions = extensions
req_extensions = extensions
[ dn ]
CN = example.test
[ extensions ]
basicConstraints = critical, CA:false
keyUsage = critical, digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth
subjectAltName = @alt_names
subjectKeyIdentifier = hash
[ alt_names ]
DNS.1 = example.test
IP.1 = 192.0.2.10
Each extension entry has the shape name = [critical, ]value(s). A multi-valued extension can use comma-separated values, or point at another section with @section_name. The latter is easier to review and is required when a value itself contains a comma.
For example, subjectAltName = @alt_names keeps the names separate from the extension list. The numeric suffixes matter: OpenSSL does not retain two identical field names in one section, so use DNS.1, DNS.2 and so on when there are several names.
3. Issue a disposable certificate
This command generates a new RSA key and a self-signed certificate. It changes state by creating two files, so use a temporary directory or replace the paths with controlled locations. No elevated privileges are needed.
openssl req -new -x509 -nodes -newkey rsa:2048 \
-keyout /tmp/x509v3-guide.key \
-out /tmp/x509v3-guide.crt \
-days 1 \
-config /tmp/x509v3-guide.cnf
Expected result: OpenSSL prints key-generation progress and exits with status 0. The certificate is valid for one day. The -nodes option leaves the generated private key unencrypted, which is convenient for a disposable test and unsafe for a key that must be retained.
Checkpoint
Confirm that the certificate contains the intended extensions.
openssl x509 -in /tmp/x509v3-guide.crt -noout -text \
| sed -n '/X509v3 extensions:/,/Signature Algorithm/p'
You should see critical Basic Constraints with CA:FALSE, critical Key Usage containing Digital Signature and Key Encipherment, TLS Web Server Authentication, and SAN entries for example.test and 192.0.2.10. The subject key identifier should be a generated hash.
4. Use the same sections for a CSR
Remove the -x509 option and write a CSR instead. Because the example includes req_extensions = extensions, the requested extensions are carried in the CSR.
openssl req -new -nodes -newkey rsa:2048 \
-keyout /tmp/x509v3-guide-req.key \
-out /tmp/x509v3-guide.csr \
-config /tmp/x509v3-guide.cnf
openssl req -in /tmp/x509v3-guide.csr -noout -text \
| sed -n '/Requested Extensions:/,/Signature Algorithm/p'
A CSR expresses a request. The CA or signing command still decides which extensions enter the final certificate. In particular, do not assume that requested extensions are copied automatically when using openssl req -x509 or a CA workflow; select the extension section explicitly and check the issued certificate.
5. Choose the right extension form
For a small value, the short form is clear:
basicConstraints = critical, CA:true, pathlen:0
keyUsage = critical, keyCertSign, cRLSign
subjectAltName = DNS:ca.example.test
pathlen:0 permits the CA to sign end-entity certificates but not subordinate CAs. It is a limit on the chain below this CA, not a request to create a zero-length certificate.
Use a referenced section for more involved data. This avoids the comma separator being mistaken for part of a URI or directory name:
[ extensions ]
subjectAltName = @names
[ names ]
subjectAltName = URI:ldap://somehost.example/CN=foo,OU=bar
The same numbered-field rule applies to email addresses, DNS names and other repeated values. If an extension is unsupported, the manpage describes an arbitrary extension syntax, but that is a security-sensitive escape hatch: use a documented OID and verify the encoded result rather than guessing.
Common traps and recovery
- Duplicate names silently override. A later entry with the same extension name overrides an earlier one. Keep one authoritative entry for each extension.
- Two email lines are not two values. Putting
email = [email protected]andemail = [email protected]in one section does not create two values; number them. - critical changes processing behaviour. Add it only when relying parties are expected to understand and enforce that extension.
- Do not hand-write the key identifier.
subjectKeyIdentifier = hashasks OpenSSL to derive it; supplying a literal hexadecimal identifier is discouraged by the manpage. - Exit status is not proof. Do not confuse a successful OpenSSL exit status with a correct certificate. Inspect the final certificate, and test it with the client software that will consume it.
To undo this tutorial's temporary state, remove only the files it created:
rm -f /tmp/x509v3-guide.cnf \
/tmp/x509v3-guide.key /tmp/x509v3-guide.crt \
/tmp/x509v3-guide-req.key /tmp/x509v3-guide.csr
Warning
Do not run that cleanup with paths changed to a directory containing real keys or certificates. If you used different paths, verify each target with ls -l before removing anything.
Done means
- Sections separated. The configuration has separate distinguished-name, extension and repeated-name sections.
- Extensions correct. The certificate has the intended CA constraint, key usages and SAN values.
- Right hook used. You know whether your command needs
x509_extensionsorreq_extensions. - Output verified. You inspected the emitted certificate rather than trusting the configuration file alone.