Guide
Renewal succeeded but browser still shows expired cert
certbot says success but browsers show expiry—compare openssl serial to disk PEM and reload every hop.
Updated Apr 2, 2026 808 words
Renewal success in logs without live TLS change means deploy, reload, or wrong tier still serves the old serial.
When this happens
certbot reports congratulations while openssl still shows last month's notAfter. Global RRDNS returns different serials depending on continent; one POP never received reload SIGHUP. War rooms celebrate certbot output pasted into chat while nobody runs openssl from the same networks customers use.
CDN or load balancer tier was never updated after origin renewed. Stale monitoring agents reporting green on disk checks deepen confusion. QA scripts hitting internal VIPs show healthy while retail users on public anycast still get the stale default cert.
First response
certbot says success but browsers show expiry—compare openssl serial to disk PEM and reload every hop.
Searches for certificate renewed but site still shows expired usually mean automated renewal failed, succeeded locally but never reached the TLS listener, or hit Let's Encrypt rate limits. The fix is almost always permissions, challenge reachability, or a missing reload—not buying a longer-lived commercial cert as the first move.
For Renewal succeeded but browser still shows expired cert, read the ACME or panel error verbatim, run a dry-run renewal if your stack supports it, then confirm what certificate strangers receive on port 443 after you deploy. Certificate files on disk lie; only live handshakes matter for users.
The classic green certbot with red browser means renewed files never reached the listener strangers use, or a CDN still serves an older edge cert. Compare serial from openssl externally to certbot certificates output before closing the ticket. Triage should capture timestamps, account URLs, and the last Boulder error type before infrastructure changes widen blast radius. Pause destructive retries until you can cite the last Boulder error type and the challenge mechanism in use; random package reinstalls rarely fix presentation gaps.
See also ACME DNS-01 challenge propagation delays and ACME HTTP-01 challenge failed 403.
Why renewal failed
Issuance updates lineage files locally; every hop must load new bytes. SRE tickets should include both certbot certificates output and ssldays/openssl from three regions to prove asymmetric deploys. Tiered architectures need tiered verification—disk, pool member, LB, CDN, user.
Default certificate on shared IP may differ from per-SNI vhost customers hit. Some terminators need full process restart to pick up chained cert changes when stapling OCSP responses cached old issuer data. Partial deploy success is failure; bake that into incident severity definitions.
Partial deploys renew one node in a pool while others age. Blue/green cutover checklists must include serial verification on both colors before traffic shift. Escalate with timestamps, hostnames, and serial comparisons attached so the next engineer does not repeat already-failed renew attempts.
Renewal success updates local lineage paths; deploy and reload steps are separate. Multi-tier setups may renew origin while edge still pins old material, or vice versa. Clarify in tickets whether failure is validation, issuance, deploy, or listener presentment—teams too often mix these phases when reading logs. Define done as matching serial everywhere, not as green CI jobs that curl internal URLs only.
Not browser cache when openssl also shows old notAfter. Not DNS when TLS handshake already presents expired leaf. Executive summaries citing certbot success without openssl proof confuse stakeholders; always pair automation logs with a handshake captured from outside the VPN.
Common causes
These patterns drive most certificate renewed but site still shows expired tickets. They explain why automation logs look green while browsers or APIs still break.
Missing nginx reload hook. IPv6-only monitoring green-lights while IPv4 anycast still serves the expired default cert on shared hosting.
Load balancer default cert not updated.
Cloudflare orange cloud serving different tier. HAProxy crt lists update only on full restart when crt-store caching enabled on older versions.
Diagnose
Compare serial from external openssl to local cert.pem. Draw a hop diagram with serial checks at each layer; most missed reloads hide on the hop operators skip because it last passed audit.
openssl x509 -noout -serial -in /etc/letsencrypt/live/HOST/cert.pem
echo | openssl s_client -connect HOST:443 -servername HOST 2>/dev/null | openssl x509 -noout -serial
Walk every layer: CDN, LB, ingress, pod, node host—but start with openssl from outside. Use openssl s_client -connect host:443 -servername host -showcerts from multiple POPs when geo DNS suspected. If a load balancer terminates TLS, check whether the target group speaks TLS again—double TLS stacks confuse serial checks.
Fix step-by-step
Follow this sequence for renewal succeeded but browser still shows expired cert. Capture command output after each step so the next responder can pick up without rerunning destructive changes.
- Confirm certbot or cert-manager wrote new files.
- Run deploy hooks and reload daemons.
- Update load balancer listener certificates.
- Invalidate CDN edge if it pins custom certs.
- Drain stale pool members.
- Re-test openssl from three networks.
- Document which hop served old material.
- Automate post-renew serial checks.
- Archive redacted ACME logs and openssl transcripts in the ticket before closing.
- Schedule a follow-up dry-run before the next natural renewal window.
- Store before/after serial screenshots from two external networks in the ticket.
Verify the live certificate
OpenSSL
echo | openssl s_client -connect HOST:443 -servername HOST 2>/dev/null | openssl x509 -noout -dates -subject -issuer
Replace HOST with the public hostname on the certificate. Run the same command after any reload or deploy change; acceptance means notAfter moved forward and serial changed versus the incident-opening capture.
How to read the output
External openssl serial vs local cert.pem serial is the decisive test. Capture serial and notAfter from at least two geographic vantage points when using anycast; one POP still serving old PEM is enough to fuel intermittent user reports.
curl
curl -vI --connect-timeout 10 https://HOST/ 2>&1 | grep -Ei 'expire|SSL certificate|subject:|issuer:'
Replace HOST with the public hostname users request.
Roll out and verify
Trace full TLS path from Internet to process. Change tickets should list each hop serial before/after in a table so auditors see reload coverage without rerunning commands.
Deploy chain everywhere; reload all hops. Update monitoring to assert serial change within N minutes of certbot success event in logs.
Automate live serial checks post-renew. Publish a short post-incident note listing which hop served stale TLS and which command proved the fix so the next renewal inherits a checklist.
What to automate
After every renew job, assert external serial equals expected from disk. Automated serial comparison after every renew job across regions.
ssldays is built for this mismatch—handshake truth beats filesystem timestamps. ssldays is designed for post-renew confirmation when logs lie.
Frequently asked questions
Multiple cert lineages?
Ensure vhost references the lineage that renewed. Put disk serial and live serial on first line.
HSTS?
Does not change need to reload—users still need valid dates. Put disk serial and live serial on first line.
Cloudflare only?
May need origin update even when edge renewed—check both layers. Put disk serial and live serial on first line.
Could OCSP stapling cause this appearance?
Rarely mistaken for expired leaf dates—clients show notAfter on the leaf. Stapling issues cause different errors. Still, recycle workers after renew in case stapling cache holds stale issuer references alongside old certs.
Monitor this hostname on a schedule
Live certificate checks and renewal reminders — not guesswork from PEM files.