Diagnose TLS failures
Diagnose certificate validity time
Use certificate dates and the client clock to distinguish expired, not-yet-valid, and clock-skew failures.
Every certificate has a notBefore and a notAfter timestamp. Verification compares them against one more input people forget about: the client’s own clock. A wrong clock can make a perfectly good deployment look invalid. And it can make a real expiry look like a mystery.
That gives us three different time failures with similar-looking errors:
- Expired: the clock is right,
notAfteris in the past. Someone missed a renewal. - Not yet valid: the clock is right,
notBeforeis in the future. Rare. Usually a certificate issued by a machine with a fast clock. - Clock skew: the certificate is fine, the client’s clock is wrong. Common on devices that sat powered off, containers with a broken time source, and machines without NTP.
Diagnosis means putting the two timestamps and the clock side by side:
openssl x509 -in app.crt -noout -dates
# notBefore=Sep 8 16:46:04 2026 GMT
# notAfter=Sep 22 16:46:04 2026 GMT
date -u
# Tue Sep 8 16:46:26 UTC 2026
Use date -u, not plain date. Certificate timestamps are in GMT, and comparing across timezones by eye is how off-by-a-few-hours mistakes happen. With the three lines together, classify: valid now, expired, or not yet valid.
For a live server, take the dates from the connection itself:
openssl s_client -connect flaviocopes.com:443 -servername flaviocopes.com </dev/null 2>/dev/null | openssl x509 -noout -enddate
# notAfter=Nov 15 15:33:25 2026 GMT
Let’s see what an expired certificate looks like to OpenSSL. Recent OpenSSL versions can backdate a certificate at signing time, so we can make one from our lab CSR:
openssl x509 -req -in app.csr -CA lab-ca.crt -CAkey lab-ca.key -CAcreateserial -out expired.crt -not_before 20260801000000Z -not_after 20260815000000Z -sha256 -copy_extensions copy
openssl verify -CAfile lab-ca.crt expired.crt
# error 10 at 0 depth lookup: certificate has expired
# error expired.crt: verification failed
Error 10 is the expiry code. In s_client it appears as Verify return code: 10 (certificate has expired). When you see 10, don’t look anywhere else first.
If the dates look sane but verification still complains about time, suspect the clock. On systemd Linux, timedatectl shows System clock synchronized: yes when NTP is working. A clock off by a few minutes already breaks certificates issued moments ago.
The prevention side is monitoring. Alert when a certificate has 14 days left, not when it dies. Expiry is the most predictable outage in computing. The date has been printed inside the certificate since the day it was issued.
One thing you must never do: change the clock to hide the error. That breaks logs, tokens, schedulers, and every other certificate on the machine. Fix time synchronization or renew the certificate. Those are the only two real repairs.
Lesson completed