TLS certificate expired — clients failing handshake
Clients get 'certificate has expired' or verification error
Focused incident mode — strips away everything but this runbook.
Symptoms
- Clients get 'certificate has expired' or verification error
- Browser shows NET::ERR_CERT_DATE_INVALID
- openssl s_client shows verify error after tomorrow/since-date check
Evidence to grab
Collect these artifacts before you change anything — they prove (or rule out) each cause.
Expiry check on the serving certificate
echo | openssl s_client -connect <HOST>:443 2>/dev/null | openssl x509 -noout -datesServer time
date -u
Diagnostic flow
Run the steps in order, marking each claim confirmed or refuted. The evidence you gather feeds the ranked causes below.
Guided decision tree is ready.
Steps with commands, expected output, and pass/fail branches.
Ranked root causes
Evidence checklist
- notAfter has passed on multiple clients
- No rotation automation or rotation broke silently
Confirm before fixing
echo | openssl s_client -connect <HOST>:443 2>/dev/null | openssl x509 -noout -enddateRead the exact notAfter
Fix
openssl s_client -connect <HOST>:443 -servername <HOST> -showcertsConfirm the new cert is served after issuing the replacement via your PKI/ACME
Resolution runbook
Apply from top to bottom after the cause is confirmed. Review every command before running it — risk tags are shown on each.
echo | openssl s_client -connect <HOST>:443 2>/dev/null | openssl x509 -noout -enddateProve the expiry
kubectl get certificate -A 2>/dev/null || trueIf cert-manager is in use, find the source Certificate
kubectl rollout restart deployment -A -l k8s-app=<app> 2>/dev/null || trueRestart workloads to remount the rotated secret
Prevention
- Automate renewal at 1/3 of lifetime (cert-manager / Let's Encrypt / internal CA).
- Alert early: min(cert.age) or Prometheus ssl_expire_days_seconds < 21.
- Ship NTP/chrony on every host and container.
- Have a kill-switch alert list for prod certificates in your calendar.
Related runbooks
Same stack, same failure family — worth a skim.
'SSL: certificate subject does not match hostname' in clients
kubectl get certificate shows Ready=False
Commands marked READ CHANGE DESTRUCTIVE — never run DESTRUCTIVE commands against production data you cannot restore. Copy → verify → execute.