Skip to content
SiteCheckTools

Articles/SSL Checker

Why Let's Encrypt Renewal Fails, and How to Fix It

A certificate inside 30 days of expiry does not mean renew it now. It means renewal already failed, and it will fail again in ninety days.

·8 min read

The tool this explains

SSL Checker

Read a site's certificate chain: issuer, validity dates, days until expiry, and whether the chain is complete.

Let's Encrypt certificates last 90 days and renew themselves. That is the deal, and it works — until it does not, and then it fails quietly for a month before anything visible happens.

The important thing to understand first: a certificate inside 30 days of expiry is not a reminder to renew. It is evidence that renewal already failed. Automated renewal begins at 30 days precisely so there is a month of margin. If you are inside that window, the automation has already tried and failed, probably several times.

Renewing by hand will fix today's problem and none of tomorrow's. In 90 days it will fail again, and the next time you might not catch it.

Confirm what is actually happening

Run the domain through the SSL Checker and read the Expiry result. Under 30 days means renewal is broken. Under 10 means it has been broken for a while and you should treat it as urgent.

Then check the machine itself, because the certificate on disk and the certificate being served are not always the same thing:

sudo certbot certificates

This lists what Certbot holds and when each expires. Compare it with what the SSL Checker reports. A mismatch is diagnostic all by itself, and it is cause 5 below.

Then force a renewal attempt and read the error, rather than guessing:

sudo certbot renew --dry-run

The dry run talks to Let's Encrypt's staging environment. It exercises the whole process — including validation — without consuming rate limits or issuing a real certificate. Whatever it complains about is your actual problem.

Cause 1: HTTP validation is being redirected or blocked

The most common cause by a distance.

To prove you control the domain, Let's Encrypt writes a file under /.well-known/acme-challenge/ and fetches it over plain HTTP on port 80. If that request does not return the file, validation fails.

Things that break it:

A blanket HTTPS redirect. Redirecting all HTTP traffic to HTTPS is correct and normal, and it is fine — Let's Encrypt follows redirects. What breaks it is a redirect that rewrites the path, or a redirect to a different host that does not serve the challenge file. A redirect from http://example.com/.well-known/acme-challenge/abc must arrive at something that still serves abc.

Port 80 closed. Somebody hardens the firewall, decides port 80 is unnecessary since everything is HTTPS, and closes it. HTTP-01 validation requires port 80 and cannot use another.

A rule that intercepts the path. A location block, a CDN page rule, a WAF, a maintenance-mode plugin, or an authentication layer sitting in front of the site. Anything that answers /.well-known/ before the ACME client does.

A CDN or proxy in front of the origin. If Cloudflare or similar terminates the connection, the challenge request never reaches your server. Either use DNS validation instead, or configure the proxy to pass /.well-known/acme-challenge/ through to the origin.

Test it directly by placing a file and fetching it over plain HTTP:

echo test > /var/www/html/.well-known/acme-challenge/test
curl -sSL http://example.com/.well-known/acme-challenge/test

If that does not print test, you have found the problem, and no amount of retrying renewal will help until it does.

Cause 2: a name on the certificate no longer resolves

A certificate covering several names requires every one of them to validate. One stale name fails the whole renewal.

This happens when a subdomain is decommissioned but left on the certificate — a staging host, an old marketing subdomain, a service you moved elsewhere. The site works fine, so nobody notices the dead name until renewal stops.

The dry run names the failing domain in its output. Remove it and reissue with only the names you still serve. With Certbot, --cert-name lets you redefine the name list on an existing certificate rather than creating a second one.

If a name now points at a different server, that server has to serve the challenge — which is often the real reason a multi-name certificate stops renewing after a migration.

Cause 3: the renewal timer is not running

Renewal is automatic only if something is actually running it. Installations get rebuilt, migrated, or restored from images, and the scheduled job does not always survive.

systemctl list-timers | grep certbot
systemctl status certbot.timer

If the timer is missing or inactive, enable it:

sudo systemctl enable --now certbot.timer

On systems using cron instead, check /etc/cron.d/certbot. In containers, check that whatever supervises renewal is still in the image — this is a frequent casualty of a Dockerfile rewrite, because the certificate is baked in at build time and nothing renews it afterwards.

Cause 4: renewal succeeds but the server keeps serving the old certificate

A subtle one, and the reason to compare certbot certificates against what the SSL Checker sees.

Renewal writes a new file. The running web server does not notice, because it read the certificate into memory at startup. Until it reloads, it keeps serving the old one — which eventually expires while a perfectly valid replacement sits on disk.

The fix is a deploy hook that reloads the server after each successful renewal:

sudo certbot renew --deploy-hook "systemctl reload nginx"

Or set renew_hook in the renewal configuration under /etc/letsencrypt/renewal/, so it applies to every future renewal rather than one invocation.

Use reload, not restart. A reload picks up the new certificate without dropping connections.

Cause 5: rate limits

Let's Encrypt limits certificates per registered domain per week. Hitting the limit is rare in normal operation but easy while debugging, because a failing renewal invites repeated retries.

If the error mentions rate limiting, stop retrying. The window is measured in days and further attempts only extend it. Use --dry-run against staging while you fix the underlying issue — it does not count.

Cause 6: the client is too old

Older ACME clients speak protocol versions that have been retired, and older Certbot builds have shipped with expired trust configurations. If renewal fails with a protocol or TLS error rather than a validation error, check the client version and update it.

Distribution packages lag. Certbot's own recommended installation route, or a maintained alternative such as acme.sh or lego, is generally more current than the version in an old LTS repository.

Switching to DNS validation

If HTTP validation keeps breaking — because of a CDN, a firewall policy, or a server not reachable on port 80 — DNS validation (DNS-01) avoids the problem entirely. Instead of serving a file, the client publishes a TXT record at _acme-challenge.example.com.

It requires an API-capable DNS provider and credentials for the client to use, which is more setup. In return it works regardless of what sits in front of your web server, and it is the only method that can issue wildcard certificates.

If you go this route, you can watch the record appear and expire with the DNS Checker — the TXT row shows exactly what validation published, which makes an otherwise opaque failure visible.

After you have fixed it

Verify with a dry run, not by waiting 90 days: sudo certbot renew --dry-run. A clean dry run means the next real renewal will work.

Monitor expiry from outside the server. The failure mode here is silence — nothing alerts you, because nothing is broken until suddenly everything is. Any monitor that checks the certificate and warns below 21 days will catch a stalled renewal with weeks to spare.

Check after infrastructure changes. Server migrations, firewall changes, adding a CDN and container rebuilds are the four events that most often break a renewal that had been working for years.

The short version

Inside 30 days means renewal already failed. Run certbot renew --dry-run and read the error rather than renewing by hand. It is usually HTTP validation being redirected or blocked, a dead name still on the certificate, a timer that stopped, or a server that never reloaded after a successful renewal.

Check this on your own domain with the SSL Checker. Free, no signup, and every result shows the raw data behind it.