The short answer
A Certbot renewal can fail in three separate places, and only one of them produces an error message. Either the scheduled job never ran, or the certificate authority could not validate the name, or the certificate renewed correctly and nothing reloaded the web server. The real reason for the middle case is written to /var/log/letsencrypt/letsencrypt.log, not to the console. Identify the layer first, because the fixes have nothing in common, and note that Let's Encrypt stopped sending expiration warning emails on June 4, 2025, so nothing will tell you which one broke.
On this page
- Which of the three layers failed
- Where the real error is written down
- HTTP-01: the four places it breaks
- DNS-01: propagation, delegation, credentials
- When the renewal never ran at all
- Renewed, and still serving the old certificate
- Rate limits: what you burned, and when it comes back
- It worked for years and then stopped
- The safety net that no longer exists
- When the honest fix is not a Certbot fix
- FAQ
Which of the three layers failed
Renewal is not one operation. A timer has to fire, a certificate authority has to agree you still control the name, and a running process has to be told to read the new file. Each of those can fail on its own, and only the middle one prints anything. Before changing a web server config, establish which layer you are in: it takes two commands and it decides everything that follows.
Start with the certificate the server is actually presenting, because that is the only thing your visitors experience. If it has already expired but the files under /etc/letsencrypt/live/ are recent, you are in layer three and no amount of validation debugging will help. If the files are as old as the certificate, the renewal either never ran or ran and failed, and the timer status separates those two.
| What you observe | Layer | Where to look next |
|---|---|---|
| Certificate expired, no log entries for weeks | Scheduler | systemctl list-timers certbot.timer |
| Failure messages in the log, repeated daily | Validation | /var/log/letsencrypt/letsencrypt.log |
| Certbot reports success, browsers still show expired | Deployment | Deploy hooks, and what the config points at |
| "Not due for renewal" on every run | Nothing is wrong | Certbot renews at about a third of lifetime remaining |
Where the real error is written down
The console prints a summary; the detail lives in /var/log/letsencrypt/letsencrypt.log. That file holds the ACME problem document the certificate authority sent back, and its detail field names the actual fault, the identifier being checked, and in the case of HTTP-01 the URL that was fetched and what came back. Reading it is almost always faster than reproducing the failure with more verbose flags.
If the renewal ran unattended, there was no console. The output went to the systemd journal, so journalctl -u certbot.service --since "7 days ago" shows what the scheduled runs reported, including the runs that never got as far as writing to the Certbot log. On systems using cron rather than a timer, the same output goes wherever cron sends mail, which on a modern minimal server is usually nowhere. That is worth knowing before you conclude nothing ran.
One habit saves a lot of time: search the log for the hostname rather than for the word "error". A renewal run touches every certificate on the machine, and a failure on one name produces a wall of unrelated output for the others.
HTTP-01: the four places it breaks
An HTTP-01 failure means the certificate authority could not fetch a token file over plain HTTP on port 80. Four different owners sit on that path, and each produces a different message: DNS pointing at the wrong host, port 80 closed at the network edge, the web server blocking or redirecting the challenge path, and a webroot that is not the directory the server actually serves. The log tells you which, and the fix is different for each.
Port 80 is the usual culprit, and it is worth testing the way the CA does rather than from inside the network. A request from somewhere else entirely, to http://example.com/.well-known/acme-challenge/test, settles in one second whether the path is reachable at all. A 404 is good news: it means the request arrived and the server answered. A timeout or a refused connection means it never got that far, and no Certbot flag will change that.
Redirects cause more confusion than they deserve. Let's Encrypt follows them, within limits it documents: a bounded number of hops, only to http or https targets, and only to ports 80 or 443, without validating the certificate on an HTTPS hop, since the request exists to obtain a valid one. A blanket redirect from port 80 to HTTPS therefore validates normally, and most of the exception rules people add for the challenge path are unnecessary. What does break it is a redirect to a non-standard port, or to a bare IP address. If you run a different certificate authority, confirm its own limits rather than assuming these carry over. The differences between the ACME challenge types decide which of these constraints apply to you at all.
DNS-01: propagation, delegation, credentials
A DNS-01 failure reports a missing or incorrect TXT record at _acme-challenge under the name being validated. Three causes account for nearly all of them: the record was written but the authoritative servers had not published it before the check ran, the API credentials the plugin uses have expired or lost scope, or the record is being written into a zone that is not the one answering queries.
Query the authoritative nameserver directly rather than a resolver. A cached negative answer at a public resolver will tell you the record is missing long after it has been published, and that sends people down the wrong path. If the record is present at the authority and validation still fails, the propagation wait in the plugin configuration is too short for your provider — raising it is the correct fix, not a workaround.
Credential failures have a distinctive shape: they start overnight, affect every name in the account at once, and appear immediately rather than after a delay. Most DNS provider tokens can be scoped to a single zone, and a token that was scoped broadly when it was created may have been tightened since by somebody doing the right thing for a different reason.
One structural trap deserves naming. A wildcard certificate originally issued with --manual and no automation hooks cannot renew unattended, by design. It will sit quietly in the renewal configuration and fail every time, and it is a common cause of a wildcard expiring on a machine where everything else renews perfectly.
When the renewal never ran at all
If certbot renew --dry-run passes and the certificate still expired, validation was never the problem. The dry run tests one layer: it runs now, as you, against the staging environment, and throws the result away. It says nothing about whether the scheduled job fires, whether it runs as a user that can read the account key, or whether anything reloaded the service afterwards.
On most current distribution packages, renewal runs twice a day from a systemd timer. Check that it exists and is scheduled with systemctl list-timers certbot.timer, and check the unit itself with systemctl status certbot.timer. The failure modes are mundane and easy to miss: a timer that was never enabled after installation, a unit masked during some unrelated troubleshooting, or a snap and a package installation on the same machine where one of them is scheduled and the other holds the certificates.
The date of the last run is the fastest evidence. If the timer claims to fire twice a day but the newest entry in the Certbot log is three months old, the job is being started and dying before it writes anything, and the journal for the service unit will say why. Permission changes under /etc/letsencrypt after a migration or a restore from backup are a frequent cause of exactly that.
Renewed, and still serving the old certificate
A web server reads its certificate at start-up and holds it in memory. A renewal replaces the files on disk and changes nothing about the running process, so until something reloads it, every handshake still presents the old certificate. Certbot exits successfully, the log looks clean, the files are newer than the problem, and visitors keep seeing an expired certificate.
The fix is a deploy hook, which runs only when a certificate actually changed, rather than a post hook that runs on every attempt. Reloading a busy web server twice a day for no reason is a small cost, but a hook that also restarts an application server is not, and the distinction stops mattering right up until it does.
The second half of this failure is a configuration that points at copies rather than at the live directory. Certbot updates the symlinks under /etc/letsencrypt/live/ on every renewal; files copied elsewhere at install time are refreshed by nothing at all. Anything reading from a copied path is serving a certificate that will never change, and a reload will not help it.
Whatever you conclude, confirm it from outside the host. The filesystem and the exit code both agree with each other and both can be wrong; only the handshake is authoritative. Reading back the leaf certificate and its remaining days with the SSL certificate checker takes a few seconds and settles the question before you go further.
Rate limits: what you burned, and when it comes back
Rate limits are a consequence of debugging, not usually a cause of the original failure. Let's Encrypt allows five failed validation attempts per hostname, per account, per hour, and the allowance refills gradually rather than resetting at the top of the hour, so a short pause returns part of your budget. A separate duplicate certificate limit permits five certificates per week for an identical set of names, and that is the one a--force-renewal loop exhausts. Renewals do not count against the certificates-per-registered-domain limit.
The practical rule follows from the numbers: stop retrying against production the moment you see a failure you do not understand. Reproduce it against the staging environment, where nothing you do counts, fix the single cause the log named, and spend one production attempt on the result. Hammering a broken configuration turns a ten-minute fix into an hour of waiting, and on a machine with several certificates it can lock out names that were never broken.
Limits are published per certificate authority and they change. The figures above are Let's Encrypt's as of September 2026; if you use a different ACME provider, its own documentation is the only reliable source for its numbers.
It worked for years and then stopped
When nothing changed on the server, something changed around it. The most common recent cause is multi-perspective validation: certificate authorities are now required to corroborate domain control from several network vantage points in different regions rather than from one. A firewall allowlist, a WAF policy or a geo-block that quietly let a single validator through now has to let all of them through, and the setup that had worked since 2019 starts failing without anyone touching it.
The rest of this category is upstream change you do not own. A hosting provider closing port 80 by default, a CDN starting to answer for a name, a DNS record edited by a colleague, an expiring API token, an operating system upgrade that replaced the packaged Certbot with a snap. None of these appear in your change log, which is why the first instinct — that the server must have changed — sends people looking in the wrong place.
There is a useful test for the whole category. If the same validation succeeds from your own network and fails for the certificate authority, the fault is in what the internet can reach, not in what your server is configured to do.
The safety net that no longer exists
Let's Encrypt ended its expiration notification email service on June 4, 2025, having announced the change that January. For a decade those messages were the informal monitoring of a very large number of small deployments: renewal broke quietly, an email arrived at twenty days, somebody fixed it. That backstop is gone, and the deployments that depended on it without anyone deciding to are the ones that now expire without warning.
The timing is awkward, because the slack is shrinking at the same time. Certbot begins renewing at roughly a third of remaining lifetime, so a 90-day certificate leaves about 30 days between the first failure and an outage. The CA/Browser Forum schedule approved in April 2025 cut the public maximum to 200 days on March 15, 2026, and cuts it to 100 days on March 15, 2027 and 47 days on March 15, 2029. Let's Encrypt short-lived certificates, valid for 160 hours and generally available since January 15, 2026, compress that window to roughly 53 hours.
The conclusion is not that automation is fragile. It is that the automation now needs its own alarm, and the alarm has to run somewhere other than the host it is watching. Checking days-remaining from outside, on a schedule, is the piece that replaces what the emails used to do — the practical shape of that is covered in what to monitor on a certificate and where to monitor it from. A renewal that fails on the first attempt and succeeds on the fourth is a normal week. A renewal that has failed every night for a fortnight is an outage nobody has noticed yet, and only the second one should page anybody.
When the honest fix is not a Certbot fix
Most renewal failures are bugs and deserve a fix. A few are structural, and fighting them wastes more time than replacing the approach. If the host is not reachable from the public internet, if the DNS is managed somewhere that offers no API, or if a change-controlled appliance cannot run an ACME client at all, then automated issuance is the wrong tool for that host and no amount of hook tuning changes it.
There is also a category ACME cannot serve regardless of your infrastructure. Let's Encrypt issues domain-validated certificates only, so a requirement for vetted organization details in the certificate subject has to be met by a certificate authority that performs that vetting. That is a procurement decision rather than a configuration one, and it is worth separating from the failure you are debugging today. If it applies, the practical starting points are the differences between the certificate types a public CA can issue and, where an audited organization identity is the actual requirement, organization-validated certificates and what their vetting involves.
For everything else, keep the automation and fix the layer that broke. A manually installed certificate on a 200-day lifetime is not a solution to a renewal problem; it is the same problem deferred, with a calendar reminder standing in for a timer, and a schedule that gets shorter every few years.