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 renew after 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.

Questions

Frequently asked

Why did certbot report success but the site still shows an expired certificate?

The new certificate renewed to disk, but the web server is still serving the old one from memory because it was never reloaded. Add a deploy hook like `--deploy-hook 'systemctl reload nginx'` so the server picks up the new cert after every successful renewal.

Will running certbot renew repeatedly to fix a failure get me rate-limited?

It can. Let's Encrypt limits duplicate certificates to 5 per week per name set. Repeatedly retrying a failing real renewal burns through that quota. Always debug with `certbot renew --dry-run`, which uses the staging environment and doesn't count against limits.

My HTTP-01 challenge keeps failing, what's the most common reason?

Port 80 not being reachable from the internet, or an HTTPS redirect intercepting the /.well-known/acme-challenge/ path before the token can be served. Confirm inbound port 80 is open and that the ACME challenge path is excluded from any redirect rules.

Catch a stalled renewal before your users do

VigilDog checks your certificates from the outside and alerts you days before expiry, so a silent certbot failure never becomes a full-page browser warning.

Your first domain is free forever