OpenSSL version note: Commands below target OpenSSL 3.x. Some flags differ in 1.x — noted inline where relevant.
Use -aes256 to protect the key with a passphrase:
openssl genpkey -algorithm RSA -out root.key -aes256 -pkeyopt rsa_keygen_bits:4096Or with Ed25519 (modern, recommended where compatibility allows):
openssl genpkey -algorithm Ed25519 -aes256 -out root.keyGenerate a self-signed Root CA certificate, valid for 10 years:
openssl req -new -x509 -sha256 -days 3650 -key root.key -extensions v3_ca -out root.crtopenssl genpkey -algorithm RSA -out ca.key -aes256 -pkeyopt rsa_keygen_bits:4096openssl req -new -key ca.key -out ca.csrUse an extension file (v3_intermediate_ca.ext) for proper CA settings:
openssl x509 -req -in ca.csr -CA root.crt -CAkey root.key \
-CAcreateserial -extfile v3_intermediate_ca.ext \
-days 3650 -sha256 -out ca.crtCombine the Intermediate CA and Root CA certificates:
cat ca.crt root.crt > ca_chain.crtopenssl genpkey -algorithm RSA -out server.key -aes256 -pkeyopt rsa_keygen_bits:4096Ensure the request includes a Subject Alternative Name (SAN):
openssl req -new -key server.key -out server.csr -config openssl.cnfUse an extension file (v3_server_cert.ext) for SAN and proper key usage:
openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key \
-CAcreateserial -extfile v3_server_cert.ext \
-days 730 -sha256 -out server.crtPublic vs internal PKI: Publicly trusted certificates issued by commercial CAs are capped at 398 days (enforced by browsers since 2020). The 730-day limit here is fine for internal/private PKI only.
For TLS 1.2 cipher suites that use DHE key exchange:
openssl dhparam -out dhparam.pem 4096TLS 1.3 note: TLS 1.3 uses X25519 for key exchange by default and does not use custom DH parameters.
dhparamis only relevant if you are still supporting TLS 1.2 DHE cipher suites.
openssl genpkey -algorithm RSA -out client.key -aes256 -pkeyopt rsa_keygen_bits:4096openssl req -new -key client.key -out client.csr -config openssl.cnfUse a custom extension file (v3_client_cert.ext):
openssl x509 -req -in client.csr -CA ca.crt -CAkey ca.key \
-CAcreateserial -extfile v3_client_cert.ext \
-days 730 -sha256 -out client.crtopenssl pkcs12 -export -certfile ca_chain.crt -in client.crt -inkey client.key -out client.p12openssl verify -verbose -CAfile ca_chain.crt server.crtopenssl x509 -noout -modulus -in server.crt | openssl sha256
openssl pkey -noout -modulus -in server.key | openssl sha256Changed from MD5 — MD5 is a broken hash algorithm. Both commands must produce the same SHA-256 output for the key and certificate to match.
For Ed25519/EC keys, use the public key fingerprint instead (modulus doesn't apply):
openssl pkey -in server.key -pubout | openssl sha256 openssl x509 -in server.crt -pubkey -noout | openssl sha256
openssl s_client -connect HOST:443 -tls1_3
openssl s_client -connect HOST:443 -tls1_2TLS 1.0 and TLS 1.1 are deprecated by RFC 8996. A server that only accepts those versions is a security finding.
openssl s_client -connect HOST:443 -cipher ${cipher}openssl s_client -connect HOST:443 -servername ${sni_host}openssl s_client -connect HOST:443 -cert client.crt -key client.key -stateDropped
-debug— it outputs raw bytes to the terminal and is rarely useful outside of deep protocol debugging.
basicConstraints = CA:TRUE, pathlen:0
keyUsage = keyCertSign, cRLSign
authorityKeyIdentifier = keyid:always,issuer:always
subjectKeyIdentifier = hash
basicConstraints = CA:FALSE
keyUsage = digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth
subjectAltName = @alt_names
[alt_names]
DNS.1 = example.com
DNS.2 = *.example.com
EC/Ed25519 key usage note: If using an EC or Ed25519 server key, replace
keyEnciphermentwithkeyAgreementin thekeyUsageline —keyEnciphermentis RSA-specific.
basicConstraints = CA:FALSE
keyUsage = digitalSignature
extendedKeyUsage = clientAuth
[req]
distinguished_name = req_distinguished_name
x509_extensions = v3_ca
req_extensions = v3_req
default_md = sha256
prompt = no
[req_distinguished_name]
C = US
ST = New York
L = New York
O = Example Organization
OU = IT Department
CN = example.com
[v3_ca]
basicConstraints = CA:TRUE
keyUsage = keyCertSign, cRLSign
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer:always
[v3_req]
basicConstraints = CA:FALSE
keyUsage = digitalSignature, keyEncipherment
subjectAltName = @alt_names
[alt_names]
DNS.1 = example.com
DNS.2 = *.example.com
- Always use SHA-256 or higher for hashing. SHA-1 and MD5 are broken.
- Protect all private keys with strong passphrases using
-aes256. - Prefer modern key algorithms — Ed25519 for new systems, P-384 for ECDSA, RSA-4096 as a fallback for broad compatibility. Avoid RSA below 3072-bit for any new key.
- Always include SANs in server certificates. CN-only certificates are rejected by all modern clients.
- Target TLS 1.3 where possible. Disable TLS 1.0 and 1.1 — both are deprecated by RFC 8996.
- Keep OpenSSL updated to the latest stable 3.x release.
- Disable weak protocols and ciphers — SSLv2, SSLv3, RC4, DES, 3DES, MD5, and SHA-1 signatures have no place in current deployments.
For further details, refer to the OpenSSL Documentation.