Skip to content

When Let’s Encrypt renewal fails: a debug checklist

A Let’s Encrypt certificate is good for 90 days, renews itself at 60, and you are supposed to forget it exists. So when the renewal fails, most people find out the worst way: a browser throwing a full-page security warning at their visitors. We fix this for clients most weeks, on cPanel, CyberPanel, DirectAdmin, and bare nginx boxes, and the failures cluster around six causes. Here is the order we check them, from most common to least, so you spend your time where the fault usually is.

Read the actual error before you touch anything

Do not guess. Run the renewal in test mode and read what it says:

certbot renew --dry-run

The dry run hits Let’s Encrypt’s staging servers, so it exercises the whole validation path without spending one of your real renewal attempts. It will name the domain that failed and, usually, the reason. “Connection refused,” “timeout during connect,” “unauthorized,” and “too many certificates already issued” are four completely different problems with four different fixes. If you skip this step and start restarting services at random, you are debugging blind. The error text is the map.

One more log worth reading: /var/log/letsencrypt/letsencrypt.log. The command line trims the detail; the log keeps the full HTTP exchange with the ACME server, including which validation URL it tried to reach and what came back.

Port 80 is blocked — the single most common cause

Let’s Encrypt’s default HTTP-01 challenge works by putting a file at http://yourdomain/.well-known/acme-challenge/ and asking their servers to fetch it. That fetch happens over port 80, plain HTTP, every time, even for a site that otherwise runs entirely on HTTPS. People lock down their firewall, close port 80 because “everything is on 443 now,” and the next renewal quietly dies.

Test it from outside the box, not from the server itself:

curl -I http://yourdomain/.well-known/acme-challenge/test

A connection timeout means something upstream is dropping port 80. Check the host firewall (ufw, firewalld, or your panel’s firewall page), the cloud provider’s security group, and any proxy in front. Hetzner and DigitalOcean both ship firewall rules that people forget they enabled. If you are behind Cloudflare, port 80 needs to reach your origin too — proxied or not — which is a trap we see constantly and one reason we keep a short list of Cloudflare rules that do not break renewals.

The domain no longer points where you think

Validation follows DNS. If the A record for the domain moved to a new host, a CDN, or a load balancer, Let’s Encrypt is knocking on a door that is no longer yours. The challenge file sits on the old server; the new one answers the request and returns a 404.

Confirm what the public internet actually sees:

dig +short yourdomain

Compare that IP to the box you are running certbot on. If they differ, you have two choices: move certificate issuance to wherever the domain now resolves, or switch to a DNS-01 challenge that proves ownership through a TXT record instead of a file fetch. DNS-01 is also the only option for wildcard certificates, and it is what we default to when a client’s DNS is stable but their web servers move around.

The webroot path changed under certbot

If you originally issued the certificate with --webroot, certbot remembers the exact directory it dropped the challenge file into. Rebuild the site, change the document root, migrate to a new panel, and that path is now wrong. Certbot writes the file to a folder the web server no longer serves, Let’s Encrypt fetches a 404, renewal fails.

The stored config lives in /etc/letsencrypt/renewal/yourdomain.conf. Open it and check the webroot_path line against where the site actually lives now. On panel-managed servers this is the failure we see after someone rebuilds an account: cPanel’s AutoSSL and a hand-rolled certbot cron fighting over the same domain, each undoing the other. Pick one issuer per domain and disable the other.

Nothing is actually running the renewal

Plenty of “renewal failed” tickets turn out to be “renewal never ran.” Certbot installs either a cron job or a systemd timer, and either can go missing after an OS upgrade, a container rebuild, or a restore from an old backup. The certificate did not fail to renew; it was never asked to.

Check the timer:

systemctl list-timers | grep certbot

If nothing shows up, the automation is gone. On a systemd box the fix is usually systemctl enable --now certbot.timer. On older setups, confirm the cron line in /etc/cron.d/certbot exists and points at a real certbot binary — snap installs and apt installs put it in different places, and a leftover cron entry pointing at the wrong one fails silently every night. This is exactly the kind of thing our server monitoring setup is meant to catch before the cert expires, not after.

You hit the rate limit

Let’s Encrypt caps you at five duplicate certificates per domain per week. Sit in a loop re-running certbot renew --force-renewal while you debug, and you will burn through that quota fast — then every attempt returns “too many certificates already issued” for up to seven days, no matter how correct your config becomes after that. This is why the very first step is --dry-run against staging: staging has its own, far looser limits, so you can iterate all you want without touching the production counter. If you are already rate-limited, the honest answer is to fix the underlying cause, then wait out the window. There is no override.

Firewall geo-blocking and multi-perspective validation

Since 2020 Let’s Encrypt validates from several network locations at once and requires most of them to succeed. If your firewall blocks traffic by country — a common hardening step on servers that only serve one region — you may pass the check from one validation node and fail from another. The renewal comes back with a partial failure that looks random because it half-works.

The Fortinet and Sophos communities are full of these, and the pattern is always the same: someone geo-restricted port 80 and did not realize Let’s Encrypt fetches from Singapore, Stockholm, and a US node in the same run. The fix is to allow the ACME challenge path through regardless of source country, or to move to DNS-01 so the whole HTTP fetch stops mattering. If you are running country-level blocking as part of a broader lockdown, it is worth having someone sanity-check the whole ruleset — a tight firewall that also breaks your certificates is a bad trade, and it is a routine part of our server security work.

When the certificate has already expired

If you are reading this after the cert expired and the site is throwing warnings, the priority changes. Renewal and issuance use the same validation, so once you have fixed the real cause from the list above, force a fresh issue:

certbot renew --force-renewal

Then reload the web server — systemctl reload nginx or apache2 — because certbot updating the file on disk does nothing until the running process re-reads it. Half the “I renewed it and it is still broken” cases are just a web server holding the old certificate in memory. Reload, then check the live cert with openssl s_client -connect yourdomain:443 -servername yourdomain and read the expiry date it reports.

Where this usually ends

Most Let’s Encrypt renewal failures are one of these six, and the --dry-run output points straight at which one. Port 80 and stale webroot paths cover the majority; the rest are DNS moves, dead cron jobs, rate limits, and geo-blocking. Work them in that order and you will rarely need step seven.

When you do — a panel that keeps re-breaking issuance, a fleet of servers where certs fail on a schedule nobody can find, an expired cert on a production store losing sales right now. That is what we handle. We keep SSL renewal green as part of ongoing server support, and if you are standing up a box from scratch, our server hardening sets up automated renewal correctly the first time so this never becomes your problem. Send us the letsencrypt.log and we will tell you which of the six it is.

Start here

Hit this one
yourself?

If any of the above is happening on your stack, send us the symptoms. We triage the same day and quote before we start.

Which layer is on fire?
Your details stay with us. Always.