Let's Encrypt certificates are short-lived on purpose, so renewal has to work every single time without you watching. When it silently stops, the first symptom is usually a browser warning on a live site. This guide is about the failure modes that show up after the initial setup worked: what changed, how to read the error, and how to fix it. If you have not set up automation yet, start with how to renew an SSL certificate and automate it, then come back here when something breaks.
How renewal actually works
Certbot is an ACME client. To get or renew a certificate it creates an order with the Let's Encrypt API, receives a challenge for each hostname, proves control of the name, and then downloads the signed certificate. Two challenge types matter for almost everyone:
| Challenge | What you prove | Needs | Wildcards |
|---|---|---|---|
http-01 | You can serve a token at http://<name>/.well-known/acme-challenge/<token> | Port 80 reachable from the public internet | No |
dns-01 | You can publish a TXT record at _acme-challenge.<name> | API or manual access to your DNS | Yes |
Certbot only renews when a certificate is close to expiry (by default, roughly 30 days or fewer left on a 90-day certificate), so the scheduled job runs often, usually twice a day, and does nothing most of the time. A failure can go unnoticed for weeks.
Step 1: confirm the scheduler is running
The most common "failure" is that nothing ever tried. Check which scheduler your install uses:
# systemd timer (typical for the snap and most distro packages)
systemctl list-timers | grep -i certbot
systemctl status certbot.timer
journalctl -u certbot.service --since "7 days ago"
# classic cron entry
cat /etc/cron.d/certbot
crontab -l
If the timer is inactive, enable it with sudo systemctl enable --now certbot.timer. If you installed with the snap and also have an old apt package, you may have two competing installs; remove one so you know which binary and which timer is real.
Then run the same dry run the timer would, which talks to the Let's Encrypt staging environment and does not consume production rate limits:
sudo certbot renew --dry-run
The log it points to, /var/log/letsencrypt/letsencrypt.log, normally holds the real reason in the last 50 lines before the failure.
Step 2: read the error type
ACME errors carry a type and detail. These are the ones you will meet most:
| Message fragment | Meaning | Where to look |
|---|---|---|
Timeout during connect (likely firewall problem) | Let's Encrypt could not reach port 80 | Firewall, security group, wrong IP in DNS |
Connection refused | Nothing listening on 80 (or 443 after redirect) | Web server stopped, wrong vhost |
Invalid response from http://... 404 | Token not served from the expected path | Webroot path, rewrite rules, CMS routing |
Invalid response ... 403 or 401 | Request blocked | WAF, basic auth, geo block, bot protection |
Fetching ... Error getting validation data | Redirect loop or redirect to an unreachable host | Redirect rules, HTTPS-only config |
DNS problem: NXDOMAIN looking up A for ... | Name does not resolve | Registrar/DNS, expired domain |
DNS problem: SERVFAIL looking up CAA for ... | Broken DNS or DNSSEC for the domain | Authoritative DNS, DS record |
CAA record for ... prevents issuance | A CAA record does not allow Let's Encrypt | Add 0 issue "letsencrypt.org" |
too many failed authorizations / too many certificates | A rate limit | See the rate limits section |
Fixing http-01 failures
Because Let's Encrypt validates from the public internet, you must test it from outside your network. Do not trust a check made from the server itself.
1. Is port 80 open? Even if your whole site is HTTPS-only, port 80 must accept connections (it can redirect). Use the port checker against your public IP, or from another machine:
curl -v http://example.com/.well-known/acme-challenge/test
You should get a response from your web server, even a 404. A timeout means a firewall or security group is dropping the traffic. Cloud providers often default to allowing 443 only; add an inbound rule for TCP 80.
2. Does DNS point at the right server? If the A or AAAA record points at an old host, the challenge lands there. Pay special attention to AAAA records: Let's Encrypt prefers IPv6 when it exists, so a stale or unreachable AAAA record breaks validation even though IPv4 works fine. Check with the DNS checker or dig +short AAAA example.com. Remove the AAAA record or make IPv6 work.
3. Do your redirects interfere? Let's Encrypt follows redirects, but only to ports 80 and 443, and a redirect to a different hostname must itself be valid. A common breakage is a redirect rule that sends every request, including /.well-known/acme-challenge/, to a login page or an application route. Exclude the path in nginx:
server {
listen 80;
server_name example.com www.example.com;
location /.well-known/acme-challenge/ {
root /var/www/letsencrypt;
default_type "text/plain";
}
location / {
return 301 https://$host$request_uri;
}
}
Then point certbot at the same directory: certbot certonly --webroot -w /var/www/letsencrypt -d example.com -d www.example.com. See also HTTP redirects explained if you suspect a loop.
4. Is a WAF or CDN in the way? Cloudflare, Sucuri and similar services can challenge or block the validation request, especially with "always use HTTPS", bot fight mode or country blocks. Create a rule that bypasses security checks for /.well-known/acme-challenge/*, or switch to dns-01.
5. Behind a load balancer? The token may be written on one backend while the request reaches another. Use shared storage, route the challenge path to one node, or use dns-01.
Fixing dns-01 failures
dns-01 is the answer for wildcard certificates, servers with no public port 80, and load-balanced fleets. Its failures are almost always about the TXT record not being visible when Let's Encrypt looks.
- The DNS plugin credentials expired or were rotated. Look for
UnauthorizedorInvalid API tokenin the log and generate a new token with permission to edit only that zone. - Propagation delay. If you use a manual hook, the record must be visible on every authoritative nameserver before certbot continues. Many plugins have a
--dns-<provider>-propagation-secondsoption. - A leftover
_acme-challengeCNAME. If_acme-challenge.example.comis a CNAME to another zone (a legitimate delegation trick), the TXT record has to be created at the CNAME target, and your credentials need access to that zone. - Split-horizon DNS or the wrong zone. Verify what the world sees:
dig +short TXT _acme-challenge.example.com @8.8.8.8
dig +short NS example.com
dig +short TXT _acme-challenge.example.com @ns1.your-dns-host.com
If the authoritative server has the record and public resolvers do not, give it time and check your TTL. A wildcard for *.example.com does not cover the bare example.com; request both.
Other causes that look like Let's Encrypt problems
- Renewal works but the site still shows the old certificate. Certbot wrote new files in
/etc/letsencrypt/live/example.com/but the web server never reloaded. Add a deploy hook:certbot renew --deploy-hook "systemctl reload nginx", or put the same command in/etc/letsencrypt/renewal-hooks/deploy/reload.sh. - The certificate was renewed but chain is wrong. Point your server at
fullchain.pem, notcert.pem. The SSL chain debugger shows what is actually served. - A stale renewal config. One broken file in
/etc/letsencrypt/renewal/(a removed site, or a missingnginx/apacheplugin after a migration) makescertbot renewreport failure. Delete it withcertbot delete --cert-name old.example.com, or install the plugin. - CAA records block issuance. Check with
dig CAA example.com. If any CAA record exists, one must permitletsencrypt.org.
Rate limits without the guesswork
Let's Encrypt applies several rate limits, such as limits on certificates issued per registered domain, on identical certificate sets, and on failed validations per hostname and account. The exact numbers and refill rates change over time, so read the current Let's Encrypt rate limits page rather than trusting a blog post, including this one.
What matters in practice:
- Test with
--dry-runor--staging. Staging has far looser limits and issues untrusted certificates. - Do not wrap certbot in a loop that retries on failure. Repeated failed validations trigger their own limit, and you will be locked out for a while when you finally fix the problem.
- Do not delete
/etc/letsencryptto "start fresh" on every redeploy of a container. Persist it in a volume; otherwise each deploy requests a new certificate for the same names and hits the duplicate-certificate limit. - If you are rate limited, the error includes a retry time. Waiting is often the only fix.
Make failures visible before expiry
Automation needs a second pair of eyes. Three layers work well together:
- Run
certbot renew --dry-runmonthly from cron and alert on a nonzero exit code. - Monitor the certificate your visitors actually receive. The free uptime monitor includes SSL expiry alerts, and the SSL checker shows the current expiry date on demand. Use the bulk SSL checker for a list of hostnames.
- Ping a heartbeat URL when a deploy hook succeeds, so a silent stop raises an alert. See the heartbeat monitoring guide.
Lifetimes are shrinking industry-wide; see SSL certificate types explained for the schedule.
Next steps
- Run
sudo certbot renew --dry-runtoday and read the output to the end. - Verify the live certificate and chain with the SSL checker and the SSL chain debugger.
- Confirm port 80 with the port checker and your records with the DNS checker.
- Turn on SSL expiry alerts in the uptime monitor.
- Read SSL/TLS certificate errors explained for the browser side of expiry.
FAQ
How often does certbot try to renew?
The packaged timer or cron job normally runs twice a day, but certbot only renews certificates that are within its renewal window, around 30 days before expiry for a 90-day certificate. Most runs do nothing.
Can I renew with port 80 closed?
Not with http-01. Use dns-01 instead, or the tls-alpn-01 challenge if your client and setup support it. Many people keep port 80 open and only redirect to HTTPS.
Why does the dry run pass but real renewal fails?
The dry run uses the staging environment, which has different rate limits and sometimes different validation infrastructure. A real failure after a passing dry run is often a rate limit, a CAA record, or a deploy hook that errors only against production files.
How do I force a renewal?
Run certbot renew --force-renewal --cert-name example.com. Use it sparingly, because each forced renewal counts toward your issuance limits.
Get notified when this guide updates
Plus occasional practical tips on uptime and monitoring, nothing else. Unsubscribe anytime.
This page is general information written by our editorial team and updated when documentation changes. Servers and providers differ, so test changes safely first. See our editorial policy and disclaimer.