Security
Expired Is the Least of Your Certificate Problems
Everyone checks a certificate's expiry date and stops there, but a cert has a dozen ways to be wrong while perfectly in-date — mismatched names, a broken chain, an untrusted issuer, a key that doesn't match — so validate the whole thing against the live service, not the calendar.
- Security
- PKI
- TLS
- Operations
When a TLS connection fails, the first thing everyone checks is the expiry date, because it’s the one certificate property that’s easy to read and easy to understand. And sometimes that’s it — the cert lapsed, renew it, done. But a certificate has a surprising number of ways to be broken while its dates are perfectly valid, and every one of them produces the same unhelpful “connection failed.” If expiry is the only thing you check, you’ll spend a long time confused by a cert that isn’t expired and still doesn’t work.
The certificate is a bundle of claims, and any one can be false
A certificate isn’t just a timer. It’s an assertion: this key belongs to this name, vouched for by this issuer, valid for these uses, during this window. Expiry is one clause. The others fail just as often and far more quietly:
- Name mismatch. The cert is for one hostname and you connected by another — a bare domain vs a wildcard, a missing Subject Alternative Name, an IP where a name was expected. Modern clients ignore the legacy common-name field entirely and check SANs, so a cert that “has the right name” in the wrong field still fails.
- Broken or incomplete chain. The leaf is fine, but the server didn’t send the intermediate that links it to a trusted root — or sent it in the wrong order. Your browser might paper over it by fetching the missing link; a headless client won’t, so it “works in Chrome” and fails everywhere else.
- Untrusted issuer. The chain is complete but ends at a CA the client doesn’t trust — an internal CA whose root was never installed, a self-signed cert, the wrong CA entirely.
- Key mismatch. The certificate and the private key it’s deployed with don’t correspond. The service often won’t even start, or serves a cert that can’t complete the handshake.
- Not yet valid. The mirror image of expiry — a cert whose start date is in the future, or a client whose clock is wrong. Both read as “invalid” with no mention of time.
- Wrong usage. The key-usage or extended-key-usage extensions don’t permit server authentication, so a strict client rejects an otherwise-perfect cert.
- Wrong file permissions. The private key is readable by the wrong users (a security problem) or unreadable by the service (a startup problem). Not a cert flaw exactly, but it lives in the same failure bucket.
“Is the cert expired?” is the question that has an easy answer. “Is the cert valid for this connection, right now, to this client?” is the question that’s actually failing.
Check it from the client’s side, against the real service
The mistake underneath all of this is validating the certificate file in isolation — opening it, reading the dates, declaring it fine. But a certificate only means something in the context of a connection: which name you asked for, which chain the server actually presents, which roots the client actually trusts. So the check that finds real problems is the one that speaks to the live service, on the real port, the way a client would.
A single openssl s_client -connect host:port -servername host does more than a dozen
file inspections: it makes the server present its chain exactly as it will in production,
lets you see the whole chain, the names, the issuer, and the verify result, and it fails
the same way your real clients will. Capture the full output and read it — the verify
return code and the chain it printed will point straight at which clause is false. Then
you’re fixing the actual problem instead of renewing a cert that was never expired.
Expiry is just the one that has a countdown
The reason expiry dominates our attention is that it’s the only failure mode with a clock on it — it announces itself in advance, so monitoring and dashboards are built around it. The others don’t count down; they’re binary and they surface at deploy time or when a client changes, which makes them feel like mysteries. They’re not mysteries. They’re the other clauses of the same assertion, and they’re checkable.
So my habit now: when TLS breaks, I don’t assume expiry and I don’t inspect the file. I ask the live endpoint what it’s actually presenting and let the verify step tell me which claim failed. Most of the time it isn’t the date. This is the practical, checklist-level companion to why a TLS handshake failure is a trust problem — the handshake is where all these claims get checked at once, and it’s why getting a certificate is a supply chain with more moving parts than a renewal date. If you’ve been burned by a cert that was perfectly in-date and still refused to connect, I’d love to hear which clause it was.