My Server Journal

What is actually inside a TLS certificate

I renewed a certificate for years without ever reading one. When a renewal finally failed in an interesting way, I had to. It turns out a certificate is a small, boring document, and openssl will show you every field.

Fetch and decode

openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null \
  | openssl x509 -noout -text | less

The -servername flag matters: it sends the hostname in the handshake (SNI), and a server hosting several names uses it to choose which certificate to present. Without it you may get a different certificate than a browser would.

The fields that matter

Subject
Who the certificate is for. Modern certificates put only CN=example.com here; the organisation fields are empty for domain-validated certificates because nobody checked an organisation.
Subject Alternative Name
The list of names the certificate is actually valid for. Browsers ignore the CN and use this list. If www.example.com is not in it, www will fail even though the CN "looks right". This was my renewal bug.
Validity
Not Before and Not After. Let's Encrypt issues for 90 days and expects automation to renew around day 60. Certificate lifetimes across the industry are shrinking; anything renewed by hand will eventually be renewed late.
Issuer
The intermediate CA that signed it. Your server must send this intermediate along with the leaf, which is why the file to deploy is fullchain.pem, not cert.pem. Missing intermediates work in your browser (it caches them) and fail for everyone else.
Public key
Increasingly an EC P-256 key rather than RSA-2048: smaller, faster, equally trusted. The private half never appears in the certificate and never leaves the server.

Quick checks I now run

# which names, and until when
openssl x509 -in fullchain.pem -noout -ext subjectAltName -enddate

# does the chain the server sends actually verify
openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null \
  | grep -E 'Verify return code|issuer='

# is what's deployed the same as what's on disk
openssl x509 -in fullchain.pem -noout -fingerprint -sha256

A Verify return code: 0 (ok) plus the right names in the SAN list is the whole story. Everything else in the decoded output — policy OIDs, CRL distribution points, the certificate transparency SCTs — is real, but it has never been the reason something broke for me.