Skip to main content

    Why Certbot Renewal Fails, and What Actually Fixes It

    A renewal fails in one of three layers, and the fix depends on which one broke. How to read the log, find the real cause, and close the silent gap.

    MS
    My-SSL Team
    ·
    15 min read
    ·
    Published September 1, 2026
    ·
    Last updated September 1, 2026

    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.

    The three layers a Certbot renewal passes through, and which one produces a visible error messageA stack of three layers. Layer one is the scheduler: a systemd timer or cron job that has to fire twice a day and run as a user that can read the Let's Encrypt directory. When it fails, nothing is printed anywhere you normally look and the certificate simply expires. Test it with systemctl list-timers certbot.timer. Layer two, highlighted in gold, is validation: the certificate authority confirms you still control the name over HTTP-01, DNS-01 or TLS-ALPN-01. This is the only layer that produces the renewal failed message, and it writes the reason to the Let's Encrypt log. Test it with certbot renew --dry-run. Layer three is deployment: the new files are written to the live directory, but the running web server keeps the old certificate in memory until something reloads it. When it fails, Certbot reports success while browsers keep seeing the expired certificate. Test it by comparing what the server sends with the file on disk. A footer notes that two of the three layers fail silently.One sentence, three different faults underneath it1 · Scheduler — did the renewal run at all?A systemd timer or cron entry has to fire, twice a day, as a user that can read /etc/letsencrypt.Fails silently.Nothing is printed. The certificate just expires on schedule.Test: systemctl list-timers certbot.timer2 · Validation — did the CA agree you still control the name?HTTP-01 over port 80, DNS-01 via a TXT record, or TLS-ALPN-01 over port 443.The only layer that talks.It writes the reason to letsencrypt.log.Test: certbot renew --dry-run3 · Deployment — is the running service using the new file?New files land in live/. The web server keeps the old certificate in memory until it reloads.Fails silently.Certbot reports success. Visitors still see the expired certificate.Test: compare what the server sends against the file on diskTwo of the three never say a word. Identify the layer before you start fixing.
    Almost every wasted hour on this problem comes from debugging layer 2 when the fault was in layer 1 or 3 — the two that produce no error at all.

    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 observeLayerWhere to look next
    Certificate expired, no log entries for weeksSchedulersystemctl list-timers certbot.timer
    Failure messages in the log, repeated dailyValidation/var/log/letsencrypt/letsencrypt.log
    Certbot reports success, browsers still show expiredDeploymentDeploy hooks, and what the config points at
    "Not due for renewal" on every runNothing is wrongCertbot 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.

    The four points where an HTTP-01 validation request breaks, and the error each one producesA left-to-right request path with four numbered break points. The certificate authority sends validation requests from several network vantage points. Break point one is DNS: if the A or AAAA record points somewhere else, the error names a connection to an address you do not recognise. Break point two, highlighted in gold, is the network edge: port 80 closed by a firewall, a security group or a hosting provider produces a connection refused or timeout, and this is the most common cause. Break point three is the web server: a redirect to a port other than 80 or 443, or a rule that blocks the well-known ACME challenge path, produces an unexpected status code. Break point four is the filesystem: the webroot passed to Certbot does not match the directory the server actually serves, producing a 404 on the token file. The chain ends at the token file under the well-known ACME challenge directory, whose contents must match the key authorization.Four owners, four different error messagesCertificate authorityseveral vantage points1 · DNSA / AAAA record2 · Port 80firewall / provider3 · Serverrules, redirects4 · Webroot/.well-known/acme-challenge/What each break point says in the log1 · DNSThe request reaches an address you do not recognise, or none at all.2 · Port 80Connection refused, or a timeout. The single most common cause.3 · ServerAn unexpected status code: a block rule, or a redirect off ports 80 and 443.4 · WebrootA 404 on the token file: the -w path is not the directory being served.
    The log tells you which of the four it was. Reading it first is faster than changing the web server config and trying again.

    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.

    What a Certbot dry run tests compared with what the scheduled renewal actually doesTwo columns. The left column is certbot renew --dry-run: it runs now, interactively, as the user typing the command, against the staging environment, and it discards the certificate it obtains. The right column is the scheduled renewal: it runs twice a day on a timer, unattended, as root or the timer's user, against production, and it writes real files and runs hooks. Below them, a highlighted gold band lists what the dry run therefore cannot prove: that the timer is enabled and firing, that the unit is not masked or failing, that the account and file permissions work for the scheduled user, that hooks run to completion, and that anything reloads the web server afterwards. A footer states the conclusion: a passing dry run alongside an expired certificate means the fault is in the schedule or the reload, not in validation.A green dry run and an expired certificate are not a contradictioncertbot renew --dry-runRuns now, because you typed itAs you, in your shellAgainst the staging environmentThrows the certificate awayProves: the CA can still validatethe name today.The scheduled renewalRuns twice a day, on a timerUnattended, as the timer's userAgainst productionWrites real files, runs hooksProves: everything else, and itis the one that has to work.What the dry run therefore cannot prove• That the timer is enabled, unmasked, and actually firing• That the scheduled user can read the account key and the certificate directory• That deploy hooks run, and that anything reloads the web server afterwardsDry run green plus certificate expired means the fault is the schedule or the reload.
    Worth knowing before you spend an afternoon on validation: the dry run and the job that actually renews your certificate share almost no machinery.

    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.

    Why a successful renewal can still serve an expired certificate to visitorsTwo parallel states after a successful renewal. On disk, the live directory now holds a new full chain and private key, written at the time the renewal ran, and Certbot exits reporting success. In memory, the web server process started weeks earlier and read the old certificate at start-up, so it keeps presenting the expired one on every handshake. The gap between the two states is highlighted in gold and labelled as the missing reload, closed by a deploy hook that reloads the service after a renewal. A note lists the second common cause: configuration pointing at copied files rather than the live directory, so the copies are never refreshed. A footer states that the only reliable test is what the server sends on the wire, not what is on the filesystem.The files are new. The process is not.On disk, 03:12 today/etc/letsencrypt/live/example.com/fullchain.pem — newprivkey.pem — newCertbot exits 0.In memory, started 41 days agoThe server read its certificate once,at start-up, and has held it since.Every handshake still presents it.Visitors see an expired certificate.missingreloadThe fix: a deploy hook that reloads the service, run only when a certificate actually changed.certbot renew --deploy-hook "systemctl reload nginx"The other half of this failure: config that points at copies, not at live/Certbot updates the symlinks under live/. Files copied elsewhere are never refreshed by anything.Test the handshake, not the filesystem.
    Both halves of this produce a perfectly clean Certbot log, which is exactly why it survives long enough to reach the expiry date.

    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.

    How much time a failed renewal leaves you, by certificate lifetimeThree horizontal bars comparing certificate lifetimes and the slack each one leaves after a renewal starts failing, given that Certbot begins renewing at roughly one third of remaining lifetime. A 90-day Let's Encrypt certificate begins renewing around day 60 and leaves about 30 days to notice and fix the problem. A 200-day certificate, the public maximum since March 15, 2026, leaves a proportionally longer window but drops to 100 days on March 15, 2027 and 47 days on March 15, 2029. A Let's Encrypt short-lived certificate, highlighted in gold, is valid for 160 hours and leaves roughly 53 hours. A note underneath records that Let's Encrypt stopped sending expiration notification emails on June 4, 2025, so no message arrives to consume that remaining time. The footer states the consequence: the slack shrinks with the lifetime while the warning that used to fill it is gone.How long a broken renewal gives you before the site breaksRENEWAL STARTSSLACK BEFORE EXPIRY90-day certificate~30 daysEnough time to notice by accident.200-day maximum, public TLS since 15 March 2026longer, for now100 days in 2027. 47 days in 2029.160-hour short-lived certificate, generally available 15 January 2026~53hA long weekend is longer than this.Let's Encrypt stopped sending expiration notification emails on 4 June 2025. Nothing arrives to spend that time on your behalf.The slack keeps shrinking. The warning that used to fill it is already gone.
    This is the reason external monitoring stopped being optional: the buffer that used to forgive a slow diagnosis is being legislated away.

    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.

    FAQ

    Frequently Asked Questions

    Get instant answers to common questions about SSL certificates and our services.

    Still Have Questions?

    Our SSL experts are available 24/7 to help with any questions about certificates, installation, or technical issues.