This guide is maintained by Ops Error Atlas from a backend engineering perspective. It favors evidence, command output, and failure-layer separation over broad definitions or blind configuration changes.
How Ops Error Atlas reviews guidescertificate has expired means a TLS client rejected a certificate because its validity period does not include the client’s current time. The expired certificate may be the leaf server certificate, an intermediate certificate, or a different certificate served because SNI or load balancer configuration selected the wrong identity.
The useful question is:
Which certificate in the chain expired, and which listener served it to this client?
Do not renew a random certificate before proving what the client actually received.
Capture the served chain
Use the same hostname and port as the failing client:
openssl s_client -connect example.com:443 -servername example.com -showcerts </dev/null
Then inspect dates:
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null \
| openssl x509 -noout -subject -issuer -dates
Also test with curl:
curl -v https://example.com/
Preserve:
- hostname;
- resolved IP;
- port;
- SNI value;
- certificate subject;
- issuer;
notBeforeandnotAfter;- verification error.
Leaf vs intermediate expiry
Common cases:
| Expired item | Signal |
|---|---|
| leaf certificate | subject matches the site; notAfter is in the past |
| intermediate certificate | leaf may look valid, but chain verification fails |
| wrong certificate | subject/SAN does not match the requested hostname |
| stale load balancer cert | direct backend differs from public listener |
| client clock wrong | certificate is valid from external checks but one client rejects it |
If only one client fails, check the client clock before assuming the server certificate is wrong.
Check SNI and resolved targets
SNI decides which certificate a TLS server may present.
Test each resolved IP while preserving SNI:
getent hosts example.com
openssl s_client -connect <ip>:443 -servername example.com -showcerts </dev/null
curl -v --resolve example.com:443:<ip> https://example.com/
Strong signals:
- one IP serves an old certificate;
- default virtual host serves an expired certificate when SNI is missing;
- direct IP test differs from hostname test;
- CDN or load balancer edge nodes are inconsistent.
Fix path:
- update the certificate on every serving endpoint;
- verify SNI configuration;
- remove stale targets;
- purge or rotate edge/load balancer certificates where applicable.
Check client time
Certificate validation depends on client time.
On Linux:
date -u
timedatectl status
Strong signals:
- only one host, container, VM, or embedded device fails;
- the client time is far in the past or future;
- NTP is disabled or blocked;
- logs have impossible timestamps.
Fix path:
- restore time synchronization;
- verify UTC time;
- re-run the same TLS command after clock correction.
Do not disable certificate verification because one machine’s clock is wrong.
Load balancer and proxy branch
TLS may terminate at several places:
client -> CDN -> load balancer -> Nginx -> app
Each TLS termination point can have a different certificate.
Check:
- CDN certificate;
- load balancer listener certificate;
- Nginx
ssl_certificate; - service mesh gateway certificate;
- backend mTLS certificate, if used;
- renewal automation scope.
Nginx:
nginx -T | grep -n "ssl_certificate"
Kubernetes:
kubectl get secret -A | grep -i tls
kubectl describe ingress <name>
The public certificate and internal mTLS certificate are separate. Renewing one does not renew the other.
Renewal automation failures
Strong signals:
- the certificate expired near a scheduled renewal window;
- DNS-01 or HTTP-01 validation failed;
- cert-manager, ACME client, or cron job logs show errors;
- renewal succeeded on one node but not another;
- new certificate exists on disk but the service did not reload.
Checks:
ls -l /etc/letsencrypt/live/<name>/
systemctl list-timers
journalctl -u certbot --since -7d
For Kubernetes cert-manager:
kubectl describe certificate <name>
kubectl describe certificaterequest <name>
kubectl logs -n cert-manager deploy/cert-manager --tail=100
Fix path:
- fix the validation failure;
- renew the certificate;
- reload or restart the TLS terminator;
- verify from outside the deployment network.
What not to do
- Do not run
curl -kand call the incident solved. - Do not renew only the certificate file if the load balancer still serves an old certificate.
- Do not test by IP without preserving SNI and assume the result matches users.
- Do not ignore intermediate certificate expiry.
- Do not assume all CDN/load balancer edges update at the same moment.
- Do not skip client clock checks when only one client fails.
Decision tree
certificate expired
|
+-- which cert expired?
| +-- leaf -> renew serving cert
| +-- intermediate -> fix chain bundle
|
+-- does every resolved IP serve same cert?
| +-- no -> stale target or edge
|
+-- does SNI change served cert?
| +-- yes -> fix virtual host/SNI config
|
+-- only one client fails?
| +-- yes -> check client clock and trust store
|
+-- renewed cert exists but users still fail?
+-- reload TLS terminator or update load balancer/CDN
Minimal incident note
hostname:
port:
client error:
client time:
resolved IPs:
SNI used:
served leaf subject:
served issuer:
notBefore:
notAfter:
expired cert in chain:
TLS termination point:
renewal automation status:
fix:
verification command:
The incident is solved when the client receives a complete valid chain from the intended listener and the verification command succeeds without disabling certificate checks.
References
- OpenSSL
s_clientdocumentation - OpenSSL
x509documentation - Nginx SSL module documentation
- cert-manager certificate documentation
Related errors
Move laterally when the first symptom points to adjacent network failures.
How to fix "certificate verify failed"
A practical certificate verify failed guide that separates hostname mismatch, expired certificates, incomplete chains, missing trust stores, containers, and runtime-specific CA behavior.
Read guideHow to debug "x509: certificate signed by unknown authority"
A practical x509 unknown authority guide that separates incomplete server chains, missing client trust stores, internal CAs, containers, and runtime-specific TLS behavior.
Read guideWhat causes TLS handshake failure
A practical TLS handshake failure guide that separates certificate chain problems, SNI mismatch, protocol and cipher mismatch, mTLS failures, and proxy TLS termination mistakes.
Read guideHow to fix "SSL wrong version number"
A practical TLS wrong version number guide that separates HTTP-to-HTTPS mismatches, TLS termination mistakes, proxy upstream schemes, STARTTLS confusion, and port mixups.
Read guideKeep one representative log line, the failing source and destination, the command output you used, and the verification command after the change. This makes the result reproducible and helps separate temporary recovery from a proven fix.
Browse related error guides