Let's Encrypt Auto-Renewal Failed: How to Diagnose and Fix It
VigilDog Team · September 13, 2026 · 6 min read
Let's Encrypt certificates last 90 days on the assumption that renewal is fully automated, which is great until the automation quietly stops and you find out only when the browser throws a full-page expiry warning. When a Let's Encrypt renewal fails, the cause is almost always one of a small set of things. Here's how to find which, and fix it.
How Let's Encrypt renewal actually works
Certbot installs a systemd timer, or a cron job, that runs certbot renew twice a day. That command is cheap: it checks every certificate and only contacts Let's Encrypt for the ones with fewer than 30 days left. So the vast majority of runs do nothing at all, which means a completely broken setup can look perfectly healthy for weeks before a certificate silently expires.
That silent failure mode is the real hazard. 'Let's Encrypt renewal failed' is bad; a renewal that was never even attempted is worse, because there's no error to notice. The steps below flush out both.
Step 1: reproduce it with --dry-run and read the log
Don't guess. Run a dry run, which performs a full renewal against the staging server without touching your rate limits or issuing a real certificate:
sudo certbot renew --dry-run
If it fails, the exact reason is in /var/log/letsencrypt/letsencrypt.logchallenge errors, connection timeouts, and plugin problems are all spelled out there. Then confirm the automation is even running: systemctl list-timers | grep certbot. If there's no timer and no cron entry, you've found the whole problem: nothing was attempting renewal in the first place.
The usual culprits
Most failures trace back to the validation challenge or a DNS change:
- Port 80 unreachable (HTTP-01): the default challenge needs inbound port 80. A firewall rule, cloud security group, or ISP block means Let's Encrypt can't fetch the validation token.
- A redirect that eats the challenge: an over-broad HTTP-to-HTTPS redirect or a reverse proxy that intercepts
/.well-known/acme-challenge/stops the token being served. Exclude that path from redirects. - DNS moved: the domain now points somewhere else, so validation lands on the wrong server. A certificate can't renew where the name no longer resolves to it.
- Standalone vs webroot mix-ups: the standalone plugin needs port 80 free, but nginx is already bound to it; or the webroot path in the renewal config no longer matches where files are actually served.
- DNS-01 challenge: expired API credentials for your DNS provider, or a TXT record that isn't being written or propagating in time.
Rate limits, dead timers, and the reload trap
Three failure modes have nothing to do with the challenge itself:
- Rate limits: Let's Encrypt allows 5 duplicate certificates per week and 50 certificates per registered domain per week. Hammering
certbot renewafter a failure can lock you out temporarily, which is exactly why you debug with--dry-run. - A timer that stopped: a server migration, an OS upgrade, or a hand-edited crontab can drop the renewal job entirely. The certificate then runs out its 90 days with nothing renewing it.
- The reload trap: renewal succeeds and a new certificate lands on disk, but the web server keeps serving the old one from memory because nothing reloaded it. Add a deploy hook:
certbot renew --deploy-hook 'systemctl reload nginx'.
Verify the fix, and don't trust it silently
After you resolve the cause, run certbot renew --dry-run once more for a clean pass, then check what the server is actually serving, not just what's on disk:
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | openssl x509 -noout -dates
Confirm the notAfter date jumped forward. You can also paste the domain into the SSL checker for the same answer without the command line.
The hard truth about automated renewal is that its failure mode is silence: everything looks fine until the morning the certificate is expired and every browser throws a warning. Independent SSL certificate monitoring is the backstop, it alerts you days ahead when a certificate stops renewing, no matter why certbot went quiet. For how that differs from plain uptime checks, see SSL monitoring vs uptime monitoring, and what to do once a certificate has already expired.
