Guide · Let's Encrypt
Certbot not renewing? Six causes and how to find yours
Checked against sources on 2026-09-29 · 4 min read
When a Let's Encrypt certificate expires, Let's Encrypt is almost never the problem. The renewal ran, or tried to run, and something around it had changed: a server, a firewall rule, a DNS record, a config file. The certificate just kept counting down until someone's browser complained.
This guide goes through six common causes, in the order we would check them. Every command is from the Certbot user guide. If you only have a minute, start with sudo certbot renew --dry-run: it runs a full renewal against the staging server, saves nothing, and prints the real error.
First, is it actually late?
Certbot renews a certificate when less than a third of its lifetime is left. It moved to that rule in version 4.0; before that it used a fixed 30 days. For today's 90-day certificates both rules mean about 30 days before expiry. From 10 February 2027 Let's Encrypt's default certificates last 64 days, so the renewal point moves to about 21 days.
So a 90-day certificate with 40 days left is fine, and one with 20 days left has missed ten days of renewal attempts. Check what the site really serves with our SSL checker, or sudo certbot certificates on the server for what is on disk.
This happens on big sites too. In our scan of the certificates on the 7,847 most-visited sites that answered (the Tranco top 10,000, 28–29 September 2026), 21.4% of certificates came from Let's Encrypt, and 5.1% of those were still being served after the point where a Certbot-style client would have replaced them. Some of those hosts renew late on purpose; from outside we can't tell which. Either way, somebody should look.
1. The renewal job isn't running
Package installs of Certbot add a systemd timer or a cron entry that runs certbot renew twice a day. Rebuild the server, move the site into a container, or install Certbot some other way, and that job may simply not exist any more. Nothing fails, because nothing runs.
systemctl list-timers | grep certbotshows the timer and when it last fired.- No timer? Look in
/etc/crontaband/etc/cron.d/. - In a container, the renewal job has to run somewhere that outlives the container.
2. The HTTP challenge can't reach the server
With the HTTP-01 challenge, Let's Encrypt fetches a file from /.well-known/acme-challenge/ on port 80 (challenge types). A site that worked last year can break this without anyone noticing:
- the webroot moved, so Certbot writes the file where the web server no longer looks;
- a new redirect rule or security plugin answers that path with a 301 to a login page or a 403;
- a firewall or cloud security group now blocks port 80;
- a CDN in front of the site answers the path itself.
The dry run shows the URL Let's Encrypt tried and what it got back. Request that same URL yourself from outside the server and compare.
3. DNS changed
If the domain now points at another server or a CDN, the challenge goes there and fails. With the DNS-01 challenge the failure is quieter: the plugin's API token belongs to the old DNS host, or it expired, so Certbot can't create the TXT record. Moving DNS is a common reason for a setup that ran for years and then stopped.
4. A CAA record leaves Let's Encrypt out
CAA records list the CAs allowed to issue for a domain (Let's Encrypt on CAA). If someone added CAA for another CA, for example while buying a certificate elsewhere, Let's Encrypt must refuse. The error in the dry run says CAA plainly. Check with dig CAA example.com and add letsencrypt.org if Let's Encrypt should still issue.
5. Rate limits after repeated failures
A broken renewal that retries in a loop can run into Let's Encrypt's rate limits, including one for failed validations. Then the fix works and the renewal is still refused for a while. Read the current limits on that page rather than from memory, test fixes with --dry-run (staging has its own, higher limits), and renew for real once.
6. It renewed, but the site serves the old certificate
This one fools people because Certbot reports success. The new certificate is on disk; nginx or Apache still has the old one in memory until it reloads. Compare the expiry date certbot certificates shows with the one our checker shows from outside. If the file on disk is newer, add a deploy hook, for example --deploy-hook "systemctl reload nginx". Certbot runs it once for each certificate it renews.
How to hear about it next time
Let's Encrypt stopped sending expiry emails in June 2025, so nobody tells you when a renewal stops. The only reliable signal comes from outside: something that connects to each hostname the way a browser does, reads the certificate it gets, and knows when that certificate should already have been replaced.
- For a handful of sites, the bulk SSL checker shows every certificate's expiry date and issuer in one table, once a week.
- For client sites, ExpiryOwl checks every certificate every 6 hours and flags a Let's Encrypt certificate that is still being served after the point where it normally renews, usually weeks before it expires. The free plan covers 5 domains.
- Whatever you use, send the alert to an address somebody reads, not the ACME account email of a developer who left.
Agencies meet all six causes at once, because client sites move hosts, DNS and CDNs without telling anyone. Our page for web design agencies covers how we group those sites by client.
How we measured
We took the Tranco list (ID 64X3X, generated 27 September 2026), tried www. and then the bare domain for each of the top 10,000 entries, and made one TLS connection on port 443 per name with our own checker: no web page was requested. "Past the usual renewal point" means a Let's Encrypt certificate that has fewer days left than a third of its lifetime minus three days (27 days for a 90-day certificate), the same line our monitor uses. We publish only totals and never name a site.