Overview

Commands below were run against OpenSSL 3.0; -noenc is the 3.0 name for -nodes, and LibreSSL (macOS) may differ. For SSH keys use ssh-keygen (ssh-config). Never commit private keys; see github-secrets.

Keys

Use ECDSA P-256 or RSA 2048 or larger for TLS.

CommandCreates
openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -out ec.keyECDSA P-256 key.
openssl genpkey -algorithm ed25519 -out ed.keyEd25519 key (not every TLS stack accepts it).
openssl genrsa -out key.pem 3072RSA key.
openssl pkey -in key.pem -pubout -out pub.pemPublic key from any private key type.

Certificates

Include a subject alternative name in every certificate you generate.

CommandCreates
openssl req -new -key key.pem -out csr.pemCSR to send to a CA.
openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:P-256 -noenc -days 365 -keyout k.pem -out c.pemKey plus self-signed certificate.

Browsers ignore the Common Name and need a subject alternative name, so add one for local development:

openssl req -x509 -newkey rsa:2048 -sha256 -days 365 -noenc \
  -keyout localhost.key -out localhost.crt -subj "/CN=localhost" \
  -addext "subjectAltName=DNS:localhost,IP:127.0.0.1"

Inspect

Inspect with -noout -text before installing any certificate.

CommandShows
openssl x509 -in c.pem -noout -textFull certificate.
openssl x509 -in c.pem -noout -dates -subject -issuerValidity, subject, issuer.
openssl x509 -in c.pem -noout -ext subjectAltNameSAN list.
openssl x509 -in c.pem -noout -fingerprint -sha256SHA-256 fingerprint.
openssl req -in csr.pem -noout -textCSR contents.
openssl verify -CAfile ca.pem c.pemChain check against a CA file.
openssl crl2pkcs7 -nocrl -certfile chain.pem | openssl pkcs7 -print_certs -nooutSubject and issuer of every certificate in a bundle.

Confirm a key matches a certificate: the two digests must be equal.

openssl x509 -noout -pubkey -in c.pem | openssl sha256
openssl pkey -pubout -in key.pem      | openssl sha256

Live TLS

Test with s_client before blaming the application.

CommandTests
openssl s_client -connect host:443Handshake, chain, and chosen cipher. SNI is sent from the hostname by default.
openssl s_client -connect host:443 -servername nameOverride the SNI name (needed for IPs).
openssl s_client -connect host:443 -tls1_3Force TLS 1.3 (-cipher sets TLS 1.2 suites, -ciphersuites sets 1.3).
openssl s_client -starttls smtp -connect host:587STARTTLS; also imap, pop3, ftp.
echo | openssl s_client -connect host:443 2>/dev/null | openssl x509 -noout -datesExpiry of a live certificate.

Convert

Convert formats with x509 and pkcs12; never edit PEM by hand.

ConversionCommand
PEM to DERopenssl x509 -in c.pem -outform DER -out c.der
DER to PEMopenssl x509 -in c.der -inform DER -out c.pem
PEM to PKCS#12openssl pkcs12 -export -out b.p12 -inkey key.pem -in c.pem -certfile chain.pem
PKCS#12 to PEMopenssl pkcs12 -in b.p12 -out all.pem -noenc (add -nokeys for certificates only)
Full chain filecat c.pem chain.pem > fullchain.pem

Gotchas

Avoid these; each makes browsers reject a certificate or a server fail to start.

GotchaFix
A certificate without a SAN is rejected by browsers, even on localhost.Always pass -addext "subjectAltName=...".
req -x509 and x509 -req default to 30 days.Set -days explicitly.
s_client keeps waiting on stdin after the handshake.Pipe echo into it, or redirect < /dev/null.
A PKCS#12 made by OpenSSL 3 may not import into old Java or Windows tools.Re-export with -legacy.
A key and certificate mismatch stops nginx or Apache from starting.Compare the public-key digests above.