Skip to content
Control Plane Labs

OpenSSL commands cheatsheet for TLS work

The OpenSSL commands worth keeping for certificate inspection, CSRs, private keys, chains, fingerprints, expiry checks, and TLS handshake debugging.

Control Plane Labs Staff

Published August 3, 2026

OpenSSL is most useful when you want to separate a certificate problem from an application problem. A browser hides many details: it follows redirects, chooses an SNI name, builds a trust path, and may reuse a session. The commands here make those choices visible. They follow the OpenSSL 3 command documentation and the certificate profile in RFC 5280.

Run commands with a copy of the certificate or key that you are allowed to inspect. Private keys are secrets. Never paste them into a ticket, shell transcript, or online decoder.

Inspect a certificate file

The first command turns a PEM certificate into readable fields:

openssl x509 -in fullchain.pem -noout -text

The fields that matter most are:

openssl x509 -in cert.pem -noout \
  -subject -issuer -serial -dates -fingerprint -sha256

openssl x509 -in cert.pem -noout -ext subjectAltName

The subject is the certificate’s distinguished name; the names a browser matches are normally in subjectAltName, not the old Common Name field. Check the SAN list before diagnosing a hostname mismatch. notBefore and notAfter are expressed in UTC. A certificate can be structurally valid and still be unusable because the current time is outside that interval.

To print only the expiry date in a monitoring job:

openssl x509 -in cert.pem -noout -enddate

The output is text such as notAfter=Aug 31 23:59:59 2026 GMT. Convert it with the date tools available on your platform, but keep the original UTC value in the alert. Do not alert only on a certificate’s file modification time; a renewal process can replace a file without changing the certificate that a server has loaded.

For a quick fingerprint comparison:

openssl x509 -in cert.pem -noout -fingerprint -sha256

Fingerprints identify the exact certificate bytes. They do not prove that a certificate is trusted, that its key is private, or that the server is presenting it for the right hostname. For those questions, inspect the SANs, chain, signature, and TLS handshake separately.

Inspect a live TLS endpoint

SNI is essential when a server hosts several names on one IP address:

openssl s_client \
  -connect www.example.com:443 \
  -servername www.example.com \
  -showcerts </dev/null

-connect selects the socket. -servername sends the hostname in the TLS Server Name Indication extension, which lets the server choose the right certificate. -showcerts prints the certificates the server sent. The OpenSSL s_client manual lists the other handshake options and explains that the command is a test client, not a complete certificate-policy checker.

Save the leaf certificate from the output and inspect it:

openssl s_client -connect www.example.com:443 \
  -servername www.example.com -showcerts </dev/null 2>/dev/null |
  awk '/BEGIN CERTIFICATE/{n++} n==1{print} /END CERTIFICATE/{if(n==1) exit}' |
  openssl x509 -noout -subject -issuer -dates -ext subjectAltName

For a verification attempt against the system trust store:

openssl s_client \
  -connect www.example.com:443 \
  -servername www.example.com \
  -verify_return_error </dev/null

The final Verify return code matters. A successful TLS handshake alone does not prove that the chain is trusted: s_client can continue after some verification errors unless -verify_return_error is set. Compare this result with a client that uses the same trust store as production.

Read a chain and verify it explicitly

A server normally sends the leaf and one or more intermediates. It should not send the root; clients already carry roots in their trust stores. Split a PEM bundle into individual certificates when you need to see the order:

awk 'BEGIN {n=0} /BEGIN CERTIFICATE/ {n++} n {print > ("cert-" n ".pem")} /END CERTIFICATE/ {close("cert-" n ".pem")}' fullchain.pem

for cert in cert-*.pem; do
  echo "== $cert =="
  openssl x509 -in "$cert" -noout -subject -issuer -dates
done

The issuer of one certificate should identify the signer of the next certificate in the path. That relationship alone is not enough: signature verification, validity periods, key usage, name constraints, and trust-anchor selection also matter. The OpenSSL verify documentation describes path validation and its trust-store options.

If you have a leaf and an intermediate bundle, verify the leaf against the intermediate:

openssl verify \
  -CAfile intermediate.pem \
  -purpose sslserver \
  -verify_hostname www.example.com \
  cert.pem

For a private internal root, provide the root as the trust anchor and the intermediate as an untrusted chain:

openssl verify \
  -CAfile internal-root.pem \
  -untrusted intermediate.pem \
  -purpose sslserver \
  -verify_hostname service.internal.example \
  leaf.pem

Do not add a downloaded certificate to the system trust store just to make a test pass. That hides the distinction between a missing intermediate, an untrusted root, and a hostname mismatch. Keep test trust material in a temporary file and remove it after the check.

Compare a private key, CSR, and certificate

A certificate does not contain the private key. You can compare public-key material without printing the secret:

openssl pkey -in server.key -pubout -outform pem | openssl sha256
openssl req  -in server.csr -pubkey -noout | openssl sha256
openssl x509 -in server.crt -pubkey -noout | openssl sha256

The three digests should match when the key, CSR, and certificate belong together. Older guides often compare RSA moduli with openssl rsa -modulus; the public-key method also works for modern key types. Protect the first command’s input file with normal filesystem permissions. The OpenSSL pkey manual documents the key-format and public-output options.

Create a private key with an explicit algorithm and inspect its public parameters:

openssl genpkey -algorithm RSA \
  -pkeyopt rsa_keygen_bits:2048 \
  -out server.key
chmod 600 server.key
openssl pkey -in server.key -text_pub -noout

Create a certificate signing request with SANs. The extension must be present in the CSR if the CA or issuance workflow copies requested extensions:

openssl req -new -key server.key -out server.csr \
  -subj "/CN=www.example.com" \
  -addext "subjectAltName=DNS:www.example.com,DNS:example.com"

openssl req -in server.csr -noout -text

A CSR proves possession of the private key corresponding to its public key; it does not prove ownership of a domain. The CA still performs its own authorization checks. Never include a private key in a CSR or send it to a CA.

Test protocol and cipher negotiation

Pin a protocol version when isolating a compatibility problem:

openssl s_client -connect www.example.com:443 \
  -servername www.example.com -tls1_2 </dev/null

openssl s_client -connect www.example.com:443 \
  -servername www.example.com -tls1_3 </dev/null

The negotiated protocol and cipher appear in the handshake summary. A failure with one version does not tell you whether the server, client, certificate, or middlebox is at fault, so compare the two runs and record the exact error. Use -alpn h2,http/1.1 when checking application protocol negotiation:

openssl s_client -connect www.example.com:443 \
  -servername www.example.com -alpn h2,http/1.1 </dev/null 2>/dev/null |
  grep -E 'Protocol|Cipher|ALPN|Verify return'

s_client is intentionally low-level. It can complete a handshake even when a browser would reject the endpoint for policy or hostname reasons. Pair it with the TLS certificate inspector and the certificate transparency lookup when you need to compare the live certificate with issuance history.

PKCS#12 and safe conversion

PKCS#12 files commonly end in .p12 or .pfx and may contain a certificate, private key, and chain. List the contents without exporting the key:

openssl pkcs12 -in bundle.p12 -info -noout

Export a certificate only:

openssl pkcs12 -in bundle.p12 -clcerts -nokeys -out cert.pem

Export the private key only when a migration requires it, then restrict and remove the output:

umask 077
openssl pkcs12 -in bundle.p12 -nocerts -nodes -out server.key
chmod 600 server.key

-nodes means the exported key is not encrypted at rest. It is convenient for automation and dangerous on a shared filesystem. Prefer an encrypted key when the receiving service supports it, and pass passwords through a secret manager rather than a shell argument. The OpenSSL pkcs12 manual describes the import and export modes.

Frequently asked questions

Why must I pass -servername to openssl s_client?+
SNI lets a server choose a certificate for the requested hostname when several sites share an IP address. Without it, you may inspect a default certificate and diagnose the wrong site.
Does a successful s_client handshake prove the certificate is valid?+
No. Check Verify return code, the hostname SAN, validity dates, key usage, and the trust store. Use -verify_return_error so verification failures stop the test.
Should a server send the root certificate?+
Usually no. A server should send the leaf and required intermediate certificates; clients use a locally trusted root. Sending the root adds bytes and does not repair an incomplete chain.
How do I know whether a CSR matches a certificate?+
Hash the public key emitted by the private key, CSR, and certificate with openssl pkey, openssl req, and openssl x509. Matching digests show that they use the same public key.
Is it safe to paste a private key into an online checker?+
No. Treat a private key as compromised if it leaves your controlled environment. Inspect certificates and CSRs locally, and rotate a key that was exposed.

Tags: #openssl, #tls, #certificates, #security, #cheatsheet