The Short Version
Let's Encrypt renewals fail in a handful of predictable ways: the ACME client stops running, the validation challenge can't reach you (blocked port 80, broken redirect, misbehaving CDN), the DNS API credential behind a DNS-01 challenge silently rotates, you hit a rate limit, or the renewal succeeds but the web server never reloads. Every one of these failure modes shares a nasty property — the system believes things are fine, or says nothing at all, until the certificate expires weeks later. This guide walks through each mode with the specific error signatures and fixes, because 'auto-renewal' is doing a lot of load-bearing work in most teams' security posture. Let's Encrypt issues 90-day certificates precisely on the assumption that renewal is automated and verified; the verification half is on you.
Related Reading
How ACME Renewal Actually Works
ACME — the Automatic Certificate Management Environment, standardized in RFC 8555 — is the protocol behind Let's Encrypt and an increasing number of other CAs. Renewal is really re-issuance: the client (certbot, acme.sh, Caddy's built-in client, lego, cert-manager) creates a new order, the CA issues a challenge proving you control the domain, the client satisfies the challenge, and the CA signs a fresh certificate. Certbot's defaults illustrate the intended rhythm: a systemd timer fires twice daily, and each run renews any certificate within 30 days of expiry. That means a healthy 90-day certificate gets replaced around day 60 — and it means roughly 30 days of scheduled renewal attempts stand between the first silent failure and an actual outage. That 30-day buffer is your entire safety margin; monitoring exists to tell you when you've started spending it.
The Three Challenge Types
HTTP-01: the CA fetches a token from http://yourdomain/.well-known/acme-challenge/<token> over port 80. DNS-01: the client publishes a TXT record at _acme-challenge.yourdomain and the CA queries for it — required for wildcard certificates. TLS-ALPN-01: validation happens inside a TLS handshake on port 443 using the acme-tls/1 ALPN protocol, useful when port 80 is impossible. Each challenge type fails differently, which is why diagnosing a renewal failure starts with knowing which one you use.
HTTP-01 Failure Modes
HTTP-01 is the most common challenge and fails for reasons that are almost always environmental rather than cryptographic. The CA must be able to make a plain HTTP request to your domain on port 80 and get the token back — anything that intercepts, blocks, or rewrites that request breaks issuance.
Port 80 Blocked or Firewalled
A security review closes port 80 ('we're HTTPS-only'), and renewals start failing on the next cycle. HTTP-01 validation requires port 80 to be reachable; redirects from 80 to 443 are fine and followed, but a connection-refused or filtered port is fatal. This failure is especially common after infrastructure moves where the new security group defaults were stricter than the old ones.
Redirect and Routing Mishaps
Aggressive redirect rules that send /.well-known/acme-challenge/ requests to a www subdomain, an app router that treats the path as a 404-able application route, or an auth middleware that demands a login for every path — all return the wrong thing to the validation server. The fix is an explicit carve-out: serve the challenge path before any rewriting, authentication, or framework routing gets a chance to touch it.
A CDN or Proxy in the Middle
Put Cloudflare or another proxy in front of an origin whose certbot expects to answer challenges directly, and validation requests may be cached, challenged with a bot check, or terminated at the edge without reaching the origin. Teams typically hit this the first renewal cycle after enabling a proxy — issuance worked at setup time, then quietly broke.
The IPv6 Trap
If your domain has an AAAA record, Let's Encrypt prefers validating over IPv6. A stale or wrong AAAA record — pointing at an old server, or at a host where the web server isn't bound to the v6 address — fails validation even though every IPv4 user finds your site perfectly. This one generates genuinely baffled debugging sessions because 'the site is up' and the renewal logs disagree.
Monitoring a Commercial SaaS?
FourSight's free plan includes 10 commercial-safe monitors with multi-region validation — free forever, no card.
Start Monitoring FreeDNS-01 Failure Modes
DNS-01 challenges trade the port-80 dependency for a DNS-provider API dependency, and that API credential becomes a load-bearing secret most teams forget exists. Wildcard certificates require DNS-01, so wildcard estates inherit all of these modes.
Rotated or Expired API Tokens
The renewal plugin authenticates to your DNS provider to publish the _acme-challenge TXT record. A routine token rotation, a security policy that expires stale credentials, or an account ownership change breaks that authentication. The renewal fails cleanly in the logs — 'unauthorized' — but nothing escalates the log line to a human. This is the single most common wildcard-renewal killer.
Nameserver Migrations
Move DNS from one provider to another and the renewal configuration still points its API calls at the old provider. The TXT record gets published — into a zone no resolver consults anymore. Validation times out. Renewal configuration is almost never on anyone's DNS-migration checklist, which is exactly why it should be.
CAA Records and Propagation Timing
A CAA record that doesn't authorize your CA (easy to introduce when centralizing DNS security settings) blocks issuance outright. Separately, slow propagation between publishing the TXT record and the CA's query can fail validation intermittently — maddening because retries sometimes succeed, so the failure looks flaky rather than structural. Set explicit propagation wait times in your client's DNS plugin configuration.
Rate Limits: The Failure You Cause Yourself
Let's Encrypt enforces rate limits to protect its free service, and renewal loops gone wrong can trip them. The published limits (as of mid-2026 — check letsencrypt.org for current values) include on the order of 50 certificates per registered domain per week and a small number of duplicate certificates for the same exact name set per week, plus limits on repeated failed validations per hour. The classic self-inflicted incident: a broken deploy pipeline re-issues instead of reusing certificates on every deploy, burns the duplicate-certificate limit, and then a legitimately needed renewal gets refused during the outage you're trying to fix. Two defenses: persist issued certificates properly so re-issuance is rare, and test everything against Let's Encrypt's staging environment, which has far higher limits and exists precisely so your experiments don't spend production quota.
The Client Itself: When Nothing Runs at All
The most silent failure of all is the ACME client that never executes. A certbot systemd timer disabled during a distribution upgrade; a cron entry that lived in a crontab wiped by a server rebuild; a container image that includes certbot but no scheduler to invoke it; a Kubernetes cert-manager deployment whose ServiceAccount lost permissions. And the near-miss variant: the client runs, renews successfully, and writes the new certificate — but the missing --deploy-hook means nginx never reloads, so the live endpoint serves the old certificate until it expires anyway. Verify both halves explicitly: 'systemctl list-timers | grep certbot' proves the schedule exists, and an external check of the served certificate proves renewals actually reach the internet.
# Is the renewal timer actually scheduled?
systemctl list-timers | grep -i certbot
# Dry-run the full renewal path against staging (safe, no quota)
certbot renew --dry-run
# What certificate is ACTUALLY being served right now?
echo | openssl s_client -servername yourdomain.com \
-connect yourdomain.com:443 2>/dev/null \
| openssl x509 -noout -subject -issuer -enddate
# Renewal with a deploy hook so the new cert actually goes live
certbot renew --deploy-hook "systemctl reload nginx"
Detecting Renewal Failures Before They Become Outages
Every failure mode above eventually produces the same observable symptom: the certificate served on port 443 keeps aging past the point where renewal should have replaced it. That makes external monitoring the universal detector. A daily-or-better check that performs a real TLS handshake and reads the served certificate's expiry catches a broken renewal roughly 30 days before impact — for a 90-day Let's Encrypt certificate renewed at day 60, an alert threshold at 25-30 days remaining fires almost immediately after the first missed renewal window. FourSight's SSL monitor does exactly this from four regions, alerting at 30, 14, and 7 days by default (configurable), with chain and hostname validation on every check. You can also add a heartbeat monitor to the renewal job itself — have the cron ping after each successful run — to distinguish 'renewal never ran' from 'renewal ran and failed,' which shortcuts the first diagnostic step at 2 AM.