- Certificates and domains
Why Let's Encrypt renewals fail, and how to catch it before users do
Automatic renewal works until it doesn't, and Let's Encrypt no longer emails you first. The usual causes, and the two checks that catch them.
Jason
Co-founder, Pingstack
Let’s Encrypt made certificates free and renewal automatic, and most of the time it works so well that nobody thinks about it. Then a browser shows a full-page warning on a Saturday, and it turns out renewal stopped working two months ago. Fun!
Two things make this more likely now than it used to be.
Let’s Encrypt stopped sending expiry reminder emails in June 2025. For years those emails were a lot of teams’ only warning. If renewal breaks today, nothing tells you unless you set something up yourself.
Certificate lifetimes are getting shorter. Let’s Encrypt certificates last 90 days today. Its default certificates move to 64 days in February 2027 and 45 days in February 2028, and a 45-day option is already available. Shorter lifetimes mean more renewals, which means more chances to fail and less time between a failure and an outage.
How renewal is supposed to work
A client like Certbot runs on a schedule, usually twice a day from a systemd timer or cron. Each run checks your certificates and renews any that are close to expiry. Certbot renews at 30 days remaining by default. Renewal means proving control of the domain again (an HTTP-01 or DNS-01 challenge), getting a new certificate, writing it to disk, and reloading whatever serves it.
Every one of those steps can fail on its own.
Eight reasons renewal fails
1. The renewal job stopped running
The server was rebuilt and the timer wasn’t enabled. Certbot moved from a distro package to a snap, or the other way, and the old timer points at nothing. The app moved into a container that doesn’t run cron. Nothing errors, because nothing runs.
2. The HTTP-01 challenge can’t reach the server
HTTP-01 needs Let’s Encrypt to fetch a file from your domain over port 80. It breaks when port 80 is closed in a firewall or security group, when a CDN or WAF blocks or caches the challenge path, when a redirect sends the request somewhere that doesn’t have the file, or when the site moved to a new host but renewal still runs on the old one.
3. Broken IPv6
If your domain has an AAAA record, Let’s Encrypt will try IPv6. A stale or misconfigured AAAA record fails validation even though the site works fine for everyone on IPv4.
4. DNS-01 credentials expired
Wildcard certificates need DNS-01, which means the client holds an API token for your DNS provider. Tokens get rotated, expire, lose permissions in a cleanup, or belong to someone who left. The next renewal fails.
5. A CAA record doesn’t allow Let’s Encrypt
CAA records list which certificate authorities may issue for a domain. If
someone adds one for another CA and leaves out letsencrypt.org, issuance
stops for that domain and its subdomains.
6. The certificate renewed, but the server still serves the old one
I’d say this is the most common one, and it’s the hardest to spot. The new certificate is on disk and Certbot reports success. Meanwhile Nginx, HAProxy or a Java app keeps serving the old certificate from memory until something reloads it. Without a deploy hook, nothing does.
certbot renew --deploy-hook "systemctl reload nginx"
Or put an executable script in /etc/letsencrypt/renewal-hooks/deploy/.
7. The certificate was copied somewhere renewal can’t reach
The same certificate was copied by hand to a load balancer, a mail server, an appliance or a second web server. Renewal updates the original. The copies expire on schedule.
8. Rate limits after repeated failures
A broken setup that keeps retrying can hit Let’s Encrypt’s rate limits, and
then the limits block your fix until the window passes. Test changes against
the staging environment first (--dry-run with Certbot).
Two checks that catch all eight
You don’t have to guess which of these will hit you. Two checks cover every one of them.
Watch the certificate the server actually serves
Check the live certificate on the hostname from outside, the way a browser sees it, and alert when its expiry gets close. This catches every failure above, including the old certificate still in memory (6) and the forgotten copy (7), because it doesn’t care what’s on disk.
Pick thresholds that fit your renewal schedule. With 90-day certificates renewed at 30 days remaining, a renewal that’s working never lets the served certificate drop below 30 days. So:
- Warning at 21 days. Renewal has been failing for over a week. Time to look, with three weeks to fix it.
- Critical at 7 days. Drop everything.
As lifetimes get shorter, renewal happens earlier, so you’ll want to move these thresholds down to match.
Check every hostname that serves a certificate, including api., mail.,
the status page domain and anything behind a different load balancer. The main
site is just the start.
Watch the renewal job itself
A served-certificate check tells you a few weeks ahead. A heartbeat on the renewal job tells you the same day. Add a ping that only runs when renewal succeeds:
#!/usr/bin/env bash
set -euo pipefail
certbot renew --quiet --deploy-hook "systemctl reload nginx"
curl --fail --silent --show-error --max-time 10 --retry 3 "$PINGSTACK_PING_URL" > /dev/null
certbot renew exits 0 when nothing needs renewing, so this pings on every
healthy run. If the timer stops (1), or a renewal attempt fails (2 to 5, 8),
the ping stops and the heartbeat check alerts after its grace period. Set the
check’s schedule to match the timer, for example every 12 hours with a
grace period of a few hours.
Setting it up in Pingstack
Pingstack’s SSL certificate checks look up the certificate on a hostname every hour and use the warning and critical days you choose. Heartbeat checks cover the renewal job; the systemd timer recipe and crontab recipe show how to wire it up. Add a domain expiry check while you’re there, since an expired domain takes the certificate down with it.
Free covers 15 checks. Start free and add the hostnames you’d least like to see a browser warning on.