Why Didn't Certbot Auto-Renew My Certificate?

Farouk Ben. - Founder at OdownFarouk Ben.()
Why Didn't Certbot Auto-Renew My Certificate? - Odown - uptime monitoring and status page

Why Didn't Certbot Auto-Renew My Certificate? The Five Stage Chain

Certbot renewal is a chain of five stages, and a failure at any one of them produces the same outcome, which is an expired certificate and no obvious explanation. The timer has to fire, the certificate has to be inside its renewal window, the challenge has to succeed, the new files have to be written, and the web server has to reload. Only the last two leave much evidence behind.

This article works through the chain in order, with the check that confirms each stage and the specific failure that most commonly breaks it. Working in this order matters, because several stages produce symptoms that look like a later problem, and diagnosing backwards wastes the time you usually do not have.

Stage one: the timer has to fire

Certbot installs a systemd timer, or a cron entry on older systems, that runs twice a day. Each run checks every managed certificate and acts only on those nearing expiry. Most runs therefore do nothing at all, which is normal and not evidence of a problem.

The failure here is that the timer is disabled, was never enabled, or was removed. This happens more often than it should: after a distribution upgrade, after switching from a package-managed Certbot to a snap or a pip install, after a container rebuild that did not carry systemd state, or after someone disabled it during troubleshooting and never re-enabled it.

Confirm it by checking the timer's status and looking at when it last ran and when it will next run. A timer that is inactive, or one whose last trigger was months ago, explains everything downstream and nothing else needs investigating until it is fixed. This is also the failure most likely to affect containerised deployments, where a certbot image without a scheduler simply never runs unless something external invokes it.

Stage two: the renewal window has to be open

Certbot does not renew a certificate just because you asked. It renews when the certificate is inside its renewal window, and historically that window is thirty days before expiry, which pairs with the ninety day lifetime Let's Encrypt has used for years.

This produces a specific confusion. Someone runs the renew command manually, sees output saying the certificate is not due for renewal, and concludes the tool is broken. It is not. It is declining to act because there is nothing to do yet. Certificates outside the window are skipped by design, and skipping is the correct behaviour rather than a failure.

The behaviour is also changing, which matters as lifetimes shrink. Certbot 4.0 introduced dynamic renewal timing tied to the certificate's actual lifetime rather than a fixed thirty days, renewing at one third of the remaining lifetime, or one half when the lifetime is under ten days. Certbot 4.1.0 added support for ACME Renewal Information, which lets the certificate authority tell the client when to renew rather than the client deciding. Both changes matter because a fixed thirty day window becomes nonsensical as certificate lifetimes drop toward the industry's 200 day, then 100 day, then 47 day schedule. If your Certbot predates 4.1.0, upgrading is the single highest-value thing you can do to this setup.

Stage three: the challenge has to succeed

This is where genuine failures concentrate, and the cause depends on which challenge type you use.

For HTTP-01, the certificate authority requests a file over plain HTTP on port 80. So port 80 has to be open and reachable from the internet, and the request path has to reach the location Certbot writes to. Teams that redirect all HTTP to HTTPS sometimes break this without realising, and teams that close port 80 entirely because they only serve HTTPS break it definitively. The validation happens over HTTP by design, and closing that port removes the only route the challenge has.

A proxy in front of your origin is the other frequent cause. If a CDN or reverse proxy intercepts the request at the edge, the challenge file never reaches your server and validation fails even though everything on your side is configured correctly. The usual fixes are switching to DNS-01 validation, which does not depend on an HTTP path at all, or installing a long-lived origin certificate so renewals stop depending on the proxy's behaviour.

For DNS-01, the failure modes are different: an expired or revoked API credential for your DNS provider, a permissions change, a plugin that no longer matches the provider's API, or propagation timing that is too slow for the wait period configured. DNS-01 is required for wildcard certificates, so wildcards inherit all of these.

Whichever type you use, a dry run is the diagnostic. It exercises the full challenge path against the staging environment without consuming rate limits or issuing anything, and it fails in exactly the way a real renewal would.

Stages four and five: writing and reloading

The last two stages are quick to check and account for a surprising share of the problem.

Writing can fail on permissions or disk space. Certbot needs to write into its configuration and live directories, and a renewal run without sufficient privileges, or on a full disk, fails at the last moment after the certificate has already been issued. That is the worst version of this problem, because it can consume issuance rate limits while producing nothing usable.

Reloading is the stage that fails most quietly, because nothing about it is Certbot's fault. Your web server reads the certificate at startup and holds it in memory. A new file on disk changes nothing until the server re-reads it, so a renewal can succeed completely while the site continues serving the old certificate right up to and past its expiry. The fix is a deploy hook, either passed on the renewal command or placed as a script in the renewal-hooks deploy directory, which reloads the server after a successful renewal. Our post on a site still showing an expired certificate after renewal works through this from the symptom end, and our guide to SSL errors covers what each browser warning tells you about which stage broke.

Common mistakes in troubleshooting certbot renewal

Assuming a skipped renewal is a failure. Certbot deliberately does nothing for certificates outside their renewal window. Most scheduled runs are no-ops, and that is the design rather than a symptom.

Closing port 80 on an HTTPS-only site. HTTP-01 validation happens over plain HTTP. Closing that port removes the only path the challenge has, and the failure appears months later at renewal time rather than immediately.

Forgetting the deploy hook. Renewal writes a file. It does not reload your web server. Without a hook, a perfect renewal still leaves the old certificate being served until something restarts the process.

Running a stale Certbot. Versions before 4.1.0 lack ACME Renewal Information support and use a fixed renewal window that ages badly as certificate lifetimes shrink. Upgrading fixes a class of problems rather than one.

Testing with force-renewal instead of a dry run. Forcing a renewal consumes rate limits and can leave you unable to reissue when you actually need to. A dry run exercises the same path against staging and costs nothing.

FAQ

Why didn't certbot auto-renew my certificate?

Because one of five stages failed: the timer did not fire, the certificate was outside its renewal window, the challenge did not validate, the files could not be written, or the web server never reloaded. Checking them in that order finds it fastest.

How often does certbot check for renewal?

The systemd timer or cron entry runs twice a day. It only acts on certificates inside their renewal window, so most runs correctly do nothing at all.

When does certbot actually renew a certificate?

Historically at thirty days before expiry. Certbot 4.0 and later use dynamic timing based on the certificate's lifetime, renewing at one third of the remaining lifetime, and 4.1.0 and later can take the timing from the certificate authority through ACME Renewal Information.

Why does renewal succeed but my site still shows the old certificate?

Because the web server loaded the certificate at startup and has not re-read the file. Add a deploy hook that reloads the server after renewal, and the new certificate takes effect immediately.

Closing thought

The frustrating quality of this failure is that it is invisible right up until it is urgent. Every stage in the chain is silent when it succeeds, and most of them are silent when they fail too, so the first real signal is a browser warning on a Monday morning. Working the chain in order turns a vague problem into a sequence of specific checks, and adding a deploy hook plus a dry run to your setup removes the two stages that break most often.

The deeper fix is not to rely on the renewal log at all. Odown checks what your host is actually presenting on a daily cycle, across unlimited SSL monitors on every plan including the twelve dollar tier, and alerts at 30, 10, and 7 days before expiry with custom thresholds available. That distinction matters here more than anywhere, because every failure in this chain produces a renewal that either did not happen or did not arrive, and only the wire tells you which.