CLOUD
Guides

Let's Encrypt Renewal Failures: Seven Causes and How to Fix Each

CLOUD|November 2, 2026|7 min read|3 views
Let's Encrypt Renewal Failures: Seven Causes and How to Fix Each

Most Let's Encrypt renewal failures come down to seven causes, from a closed port 80 and a CDN proxy to a service that never reloaded the new certificate. Start with certbot renew --dry-run, then monitor expiry externally at 14 days.

A Let's Encrypt certificate is valid for only 90 days, and Certbot tries to renew it automatically once 30 days remain, which gives you roughly 60 twice-daily attempts before visitors see a "connection not secure" warning. The trouble is that a failed renewal fails quietly, and nobody reads /var/log/letsencrypt/letsencrypt.log until an angry customer calls. The direct answer: nearly every failure traces back to one of seven causes, namely port 80 closed at the firewall, a CDN proxy intercepting the validation request, a stale AAAA record pointing Let's Encrypt at an IPv6 address that does not answer, hitting rate limits, a webroot path that changed after a redeploy, a renewal timer or cron entry that is disabled, and a web server that renewed the certificate on disk but is still serving the old one from memory. Always start with one command that tells you the truth without burning your limits: certbot renew --dry-run. It runs the full renewal against the staging server and prints the exact reason it failed. Match that message against the seven causes below, then add monitoring that alerts you at least 14 days before expiry so this does not happen again.

How renewal actually works

When Certbot asks for a new certificate, the Let's Encrypt server hands it a random token and asks it to place that token somewhere that proves you control the domain. With the HTTP-01 challenge the token goes into a file under /.well-known/acme-challenge/, and Let's Encrypt fetches it from several vantage points around the world over port 80. With DNS-01 the token goes into a TXT record named _acme-challenge, and this is the only option for wildcard certificates. Understanding this step cuts your diagnosis in half, because almost every common cause means the same thing: the request coming from Let's Encrypt never reached the file Certbot wrote.

On most modern distributions Certbot ships with a systemd timer called certbot.timer that runs twice a day with a random delay, and snap installs name it snap.certbot.renew.timer. Older setups rely on a line in /etc/cron.d/certbot. Confirm which one you have before assuming renewal works.

The seven causes and the fix for each

1. Port 80 is closed

Plenty of security teams close port 80 once HTTPS is live on the reasoning that everything is encrypted now, and that breaks HTTP-01 immediately because Let's Encrypt only starts validation over port 80. You will see Timeout during connect or Connection refused. Open port 80 and redirect everything to HTTPS except the challenge path, check the host firewall with ufw status or nft list ruleset, and remember any external provider firewall as well.

2. A CDN proxy intercepts the challenge

If the domain sits behind Cloudflare or another CDN with "Always Use HTTPS" or strict WAF rules, the proxy may answer the challenge itself or block it as bot traffic. The giveaway is an error mentioning an IP address that belongs to the CDN rather than your server. In order of preference: exclude /.well-known/acme-challenge/* from redirect and protection rules, switch to DNS-01 using your DNS provider's plugin, or use the provider's own origin certificate between the proxy and your server.

3. A stale AAAA record

Let's Encrypt prefers IPv6 when an AAAA record exists. If you moved the site to a new server, updated the A record and forgot the old AAAA record, validation goes to an address that is not yours and fails even though IPv4 is perfectly healthy. Run dig AAAA example.com +short, compare the result with the server's real addresses from ip -6 addr, then delete or correct the record.

4. Rate limits

Let's Encrypt allows five duplicate certificates for the same set of names per 168 hours, five failed validations per account and hostname per hour, and 50 certificates per registered domain per week. The usual way people hit these is a deploy script that requests a fresh certificate on every run instead of renewing, or repeated experiments against production. The message is explicit: too many certificates already issued. Always test with --dry-run or --staging, and wait out the window instead of retrying, since retries only extend the block.

5. The webroot path changed

On first issuance Certbot stores its settings in /etc/letsencrypt/renewal/example.com.conf, including the directory where it writes the challenge file. Move the site from /var/www/html to /var/www/app/public, as happens with Laravel, and Certbot keeps writing into the old directory while Nginx serves the new one, so you get a 404. Edit webroot_path in the renewal file, or better, add a fixed Nginx location for the challenge path that points to a dedicated directory untouched by deploys. That is the cleanest long-term fix.

6. The timer is disabled

After a distribution upgrade or a move from the apt package to snap, the timer can disappear or stay disabled. Run systemctl list-timers | grep certbot, and if nothing shows up or there is no next run time, renewal has not been running since that upgrade. Enable it with systemctl enable --now certbot.timer and review the last run with journalctl -u certbot.service -n 50.

7. The certificate renewed but the service never reloaded

This one confuses people the most, because certbot certificates reports 89 days of validity while the browser says the certificate expired. Nginx, Apache, Postfix and Dovecot load certificates at startup only, so they keep serving the old copy from memory. The fix is a permanent deploy hook: drop a script into /etc/letsencrypt/renewal-hooks/deploy/ that runs systemctl reload nginx plus every other service using the certificate. Mail servers are the ones people forget here.

Quick diagnosis table

Log messageLikely causeCheck with
Timeout during connectPort 80 closed or wrong IPv6curl -I http://example.com/.well-known/acme-challenge/test
Invalid response 404Webroot changedgrep webroot /etc/letsencrypt/renewal/*.conf
Invalid response 403 from an unknown IPCDN proxy or WAFdig A example.com +short
too many certificatesRate limitscertbot certificates
No recent log entries at allTimer disabledsystemctl list-timers
Log says success, browser refusesService not reloadedopenssl s_client -connect example.com:443

Monitor expiry from the outside

The most important lesson is that a successful renewal on disk proves nothing, since what matters is the certificate a visitor actually receives. Write a small check that connects to port 443 and reads the expiry date, for example openssl s_client -connect example.com:443 -servername example.com piped into openssl x509 -noout -enddate, then computes the days left and alerts when fewer than 14 remain. Why 14? Certbot starts renewing at 30 days, so reaching 14 means 16 days of attempts have already failed, which still leaves enough time to fix things calmly. Check every port that serves a certificate: 443 for the web plus 465, 587 and 993 for mail, because an expired mail certificate makes your messages stumble before they reach the inbox at strict recipients.

If you run ten or more domains, put the check into a single script that reads a list of hostnames, runs daily from a timer, and posts the result to the alert channel your team actually watches rather than a neglected mailbox. The check takes under a second per domain and removes any dependence on Let's Encrypt's expiry emails, which the service stopped sending in 2025 anyway.

Where this fits in your stack

On managed cloud hosting you need none of this, because cPanel issues AutoSSL certificates, renews them and reloads services on its own, with plans starting at 2,900 SAR per year including VAT. On a cloud server you own the whole chain, and this checklist is where it pays off. The Starter plan (1 vCPU, 2 GB RAM, 20 GB SSD) at 3,450 SAR per year comfortably runs a site or API with Certbot and Nginx, and roughly 8ms latency from Riyadh keeps your monitoring checks fast and accurate. Full details are on the pricing page.

Your checklist for today

  • Run certbot renew --dry-run on every server and read the output line by line.
  • Confirm the timer exists and has a next run with systemctl list-timers.
  • Open the files in /etc/letsencrypt/renewal/ and verify the webroot and validation method.
  • Add a deploy hook that reloads every service using the certificate, mail included.
  • Review A and AAAA records for each domain after any migration.
  • Turn on a daily external check that alerts at 14 days remaining.

These six steps take about 30 minutes per server and turn your certificate from a time bomb every 90 days into something you never think about, because you will learn about a problem at least two weeks before your visitors do.

Share