How to read a TLS certificate
A field guide to the X.509 fields that actually matter — SAN, EKU, validity, basic constraints, SCTs — and the misreads that waste an afternoon of debugging.
Ben Ennis
Published July 27, 2026
A certificate is a signed assertion that a public key belongs to a set of names. That is
the whole idea. The reason reading one feels hard is that the encoding is ASN.1 from
1988, the field names are lawyerly, and about a third of what openssl x509 -text prints
is historical sediment that no modern client looks at.
This is the short version of what to look at and in what order, plus the misreads that send people down the wrong debugging path.
Getting the certificate in front of you
If you have the file, and it starts with -----BEGIN CERTIFICATE-----, it is PEM and
OpenSSL will read it directly. If it is binary, it is DER and you need -inform der.
openssl x509 -in cert.pem -text -noout
openssl x509 -in cert.der -inform der -text -noout
If you only have a hostname, pull it off the wire. The -servername flag sends SNI,
which you almost always need — without it a shared-IP host will hand you the default
vhost’s certificate and you will spend twenty minutes wondering why the names are wrong.
openssl s_client -connect example.com:443 -servername example.com </dev/null \
| openssl x509 -text -noout
The printing options are documented in the
openssl x509 manual; -dates,
-subject, and -ext subjectAltName are useful when you want one field instead of the
whole dump. If you would rather not shell out, the
TLS certificate inspector on this site parses the same structure.
The five fields that decide whether it works
Subject Alternative Name. This is the list of names the certificate is valid for, and
it is the only field a client uses for hostname matching.
RFC 9525, which obsoleted RFC 6125 in
November 2023, is unambiguous: “The Common Name RDN MUST NOT be used to identify a
service.” The CN= in the Subject line is decoration. If a name is not in the SAN
extension (RFC 5280 §4.2.1.6),
the certificate does not cover it, no matter what the Subject says.
Validity. Not Before and Not After, always in UTC. Two things bite here. First,
a certificate that “just started failing” at midnight is usually an expiry, and a
certificate that fails immediately after issuance is usually a clock skew problem on the
client, not a bad cert. Second, the window is much shorter than it used to be. Under
CA/Browser Forum ballot SC081v3,
the maximum validity for a publicly-trusted certificate dropped to 200 days in March 2026,
on the way to 100 days in 2027 and 47 days in 2029. Let’s Encrypt already offers a
160-hour shortlived profile.
Any runbook that says “renew annually” is now wrong.
Issuer. The DN of the CA that signed this. Useful mostly as a pointer to the next certificate up the chain, and as a fast way to spot that your server is presenting a cert from a CA you did not expect.
Basic Constraints. CA:TRUE means this certificate can sign other certificates;
CA:FALSE means it is an end-entity leaf. A leaf certificate presented where an
intermediate belongs, or the reverse, produces a chain error that reads like a trust
problem. Check this before you start reinstalling root stores.
Extended Key Usage. The list of purposes the certificate is allowed to serve, defined
in RFC 5280 §4.2.1.12.
For a web server you want TLS Web Server Authentication. This field became load-bearing
in 2026: Let’s Encrypt
removed the TLS Client Authentication EKU
from its default classic profile on 11 February 2026 and retired the tlsclient
profile entirely on 8 July 2026, following Chrome’s requirement to split client and
server auth into separate PKIs. If you were quietly using a public Let’s Encrypt cert as
an mTLS client credential, it stopped validating at the peer — with no renewal error to
warn you.
Reading the chain, not just the leaf
A handshake failure is more often a chain problem than a leaf problem. What the server sends is a list: the leaf first, then whatever intermediates the operator configured. The root is not sent; the client already has it, or it does not.
openssl s_client -connect example.com:443 -servername example.com -showcerts </dev/null \
| grep -E 's:|i:'
Read it as a linked list and check that each i: (issuer) matches the next s:
(subject). A gap means a missing intermediate — the classic “works in my browser, fails
in curl” bug, because browsers will chase the AIA extension to fetch the missing link and
most command-line clients will not.
This is also why intermediate pinning is a bad idea. Let’s Encrypt now picks an intermediate at random per issuance specifically to discourage it, and rotated to a new “Generation Y” hierarchy — YE1, YE2, YR1, YR2 — in 2026. Its active certificates page is the authoritative list of what is currently in rotation.
What the extensions tell you
Past the five fields above, a few extensions are worth a glance.
Signed Certificate Timestamps. Embedded proof that the certificate was submitted to
Certificate Transparency logs, per RFC 6962.
Chrome rejects a publicly-trusted certificate without them. Their presence also means the
certificate’s SANs are public — worth remembering before you name a host
payroll-db-restore.example.com.
CRL Distribution Points, and the absence of an OCSP URL. If you are looking at a
recent certificate and there is no OCSP - URI under Authority Information Access, that
is expected, not broken. Let’s Encrypt
shut down its OCSP responders on
6 August 2025 and publishes revocation exclusively via CRLs. Leaving ssl_stapling on in
nginx against a cert with no OCSP URL logs errors forever and staples nothing.
Key Usage and Subject Key Identifier / Authority Key Identifier. The first
constrains what the key itself may do (digitalSignature, keyEncipherment); the second
pair are the hints a verifier uses to build the chain. You rarely need them until you are
debugging a path-building failure, at which point they are the first thing to check.
Misreads that cost the most time
Wildcards are one label deep. *.example.com covers api.example.com and does not cover
a.b.example.com or the bare example.com. The bare apex needs its own SAN entry.
A certificate being valid is not the same as it being trusted. openssl x509 will
happily print a self-signed certificate with a perfect-looking Subject and SAN. Only
openssl verify or a real handshake tells you whether a client will accept it.
Fingerprints are of the DER encoding, not the PEM text. Re-wrapping a PEM file or changing its line endings does not change the SHA-256 fingerprint. If two files have different fingerprints, they are genuinely different certificates.
And finally: the certificate is not the private key. A cert that parses fine while the
server refuses to start usually means the key does not match. openssl x509 -noout -modulus
and openssl rsa -noout -modulus should hash identically.
Frequently asked questions
Why does my browser trust the certificate but curl doesn't?+
openssl s_client -showcerts and confirm every issuer matches the next subject.The Common Name is right but the browser still says the name doesn't match. Why?+
My certificate has no OCSP URL. Is it broken?+
ssl_stapling on configured, turn it off — it cannot staple anything and will log errors indefinitely.How long is a publicly-trusted certificate allowed to last now?+
shortlived profile available.What's the difference between PEM and DER?+
-----BEGIN CERTIFICATE----- headers. Same certificate, same fingerprint — the fingerprint is always computed over the DER bytes. Convert with openssl x509 -inform der -outform pem.Read next
- Certificate Transparency explained for devs
- Why your ACME renewal quietly broke last month
- Try the tool: TLS certificate inspector
Tags: #tls, #x509, #openssl, #certificates