Fix: Certbot Certificate Renewal Failed (Let's Encrypt)
Quick Answer
How to fix Certbot certificate renewal failures — domain validation errors, port 80 blocked, nginx config issues, permissions, and automating renewals with systemd or cron.
The Error
Running certbot renew or certbot renew --dry-run fails with one of these errors:
Attempting to renew cert (example.com) from /etc/letsencrypt/renewal/example.com.conf produced an unexpected error: Problem binding to port 80: Could not bind to IPv4 or IPv6.Or the HTTP-01 challenge fails:
Challenge failed for domain example.com
http-01 challenge for example.com
Cleaning up challenges
IMPORTANT NOTES:
- The following errors were reported by the server:
Domain: example.com
Type: connection
Detail: Timeout during connect (likely firewall problem)Or DNS validation fails:
Domain: example.com
Type: dns
Detail: DNS problem: SERVFAIL looking up CAA for example.comOr the certificate is near expiry but auto-renewal didn’t run:
Your certificate and chain have been saved at /etc/letsencrypt/live/example.com/fullchain.pem
Expiry date: 2026-04-01 (EXPIRED or about to expire)Why This Happens
Let’s Encrypt uses ACME challenges to verify domain ownership. The most common failure causes:
- Port 80 blocked — the HTTP-01 challenge requires port 80 to be open and reachable from the internet. Firewalls, security groups, or nginx already binding port 80 block the standalone authenticator.
- Nginx webroot misconfiguration — the
/.well-known/acme-challenge/path is not served by nginx, blocked by areturn 301redirect, or points to the wrong directory. - DNS not propagated — the DNS-01 challenge or CAA record lookup fails because DNS changes haven’t propagated, or the domain has a CAA record that excludes Let’s Encrypt.
- Certbot timer not running — the systemd timer or cron job that triggers
certbot renewis disabled or misconfigured, so the certificate expires without being renewed. - Rate limits hit — Let’s Encrypt enforces rate limits (5 duplicate certificates per week). Testing with production causes failures; use
--dry-runand the staging environment. - Certbot version outdated — old Certbot versions use deprecated APIs. Let’s Encrypt eventually drops support, causing all renewals to fail.
Fix 1: Test Before Fixing
Always run a dry run first to diagnose without hitting rate limits:
# Dry run — tests the full renewal process without issuing a certificate
certbot renew --dry-run
# Dry run for a specific domain
certbot renew --dry-run -d example.com
# Verbose output to see exactly where it fails
certbot renew --dry-run --verboseCheck the current certificate status:
# List all certificates and their expiry dates
certbot certificates
# Output:
# Found the following certs:
# Certificate Name: example.com
# Domains: example.com www.example.com
# Expiry Date: 2026-06-15 09:00:00+00:00 (VALID: 89 days)
# Certificate Path: /etc/letsencrypt/live/example.com/fullchain.pemFix 2: Fix Port 80 Blocked (HTTP-01 Challenge)
The HTTP-01 challenge requires port 80 to be accessible from the internet. Check what’s blocking it:
# Check if something is listening on port 80
sudo ss -tlnp | grep :80
# Check if nginx is running on port 80
sudo systemctl status nginx
# Test port 80 from outside your server (run from another machine or use a web tool)
curl -I http://example.com/.well-known/acme-challenge/testIf nginx is running, use the webroot plugin instead of standalone:
# Webroot plugin — lets nginx keep running while renewing
certbot renew --webroot -w /var/www/html
# Or renew with webroot for a specific cert
certbot certonly --webroot -w /var/www/certbot -d example.com -d www.example.comIf using standalone and nginx blocks port 80 — stop nginx during renewal:
# Stop nginx, renew, restart nginx
sudo systemctl stop nginx
sudo certbot renew
sudo systemctl start nginxAutomate stop/start with deploy hooks:
# /etc/letsencrypt/renewal-hooks/pre/stop-nginx.sh
#!/bin/bash
systemctl stop nginx# /etc/letsencrypt/renewal-hooks/post/start-nginx.sh
#!/bin/bash
systemctl start nginxchmod +x /etc/letsencrypt/renewal-hooks/pre/stop-nginx.sh
chmod +x /etc/letsencrypt/renewal-hooks/post/start-nginx.shCheck firewall rules:
# UFW — allow port 80 and 443
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw status
# iptables — check rules
sudo iptables -L -n | grep -E "80|443"
# AWS EC2 — check security group allows inbound TCP port 80
# GCP — check firewall rules for port 80
# Azure — check Network Security GroupFix 3: Fix Nginx Webroot Configuration
If using the webroot authenticator, nginx must serve the /.well-known/acme-challenge/ path correctly:
# /etc/nginx/sites-available/example.com
server {
listen 80;
server_name example.com www.example.com;
# Required for Certbot webroot challenge
location /.well-known/acme-challenge/ {
root /var/www/certbot; # Must match --webroot-path
allow all;
}
# Redirect everything else to HTTPS — but keep the challenge path accessible over HTTP
location / {
return 301 https://$host$request_uri;
}
}Common Mistake: Putting
return 301 https://...before the/.well-known/acme-challenge/block redirects the ACME challenge to HTTPS, causing the HTTP-01 challenge to fail. Always place the challenge location block before the redirect.
Verify the challenge path is reachable:
# Create the webroot directory
sudo mkdir -p /var/www/certbot
# Test that nginx serves files from it
echo "test" | sudo tee /var/www/certbot/.well-known/acme-challenge/test
curl http://example.com/.well-known/acme-challenge/test
# Should return: test
# Clean up
sudo rm /var/www/certbot/.well-known/acme-challenge/testCheck the renewal configuration file to verify webroot path:
cat /etc/letsencrypt/renewal/example.com.conf
# Look for:
# authenticator = webroot
# webroot_path = /var/www/certbotIf the path is wrong, edit it or re-run certbot certonly --webroot -w /correct/path -d example.com.
Fix 4: Fix DNS Issues (CAA Records and DNS-01 Challenge)
Check for CAA records that block Let’s Encrypt:
# Check CAA records
dig CAA example.com
# Expected output if you have a CAA record allowing Let's Encrypt:
# example.com. 300 IN CAA 0 issue "letsencrypt.org"If you have a CAA record that doesn’t include letsencrypt.org, add one:
# DNS zone — add CAA record
example.com. 300 IN CAA 0 issue "letsencrypt.org"
example.com. 300 IN CAA 0 issuewild "letsencrypt.org"Check DNS propagation:
# Check if your domain resolves to the right IP
dig A example.com
nslookup example.com
# The IP must match the server where Certbot is running
curl -4 ifconfig.me # Your server's public IPFor DNS-01 challenge (wildcard certs), use a plugin that supports your DNS provider:
# Install DNS plugin for your provider
pip install certbot-dns-cloudflare
pip install certbot-dns-route53
# Cloudflare example
certbot certonly \
--dns-cloudflare \
--dns-cloudflare-credentials ~/.secrets/certbot/cloudflare.ini \
-d example.com \
-d "*.example.com"# ~/.secrets/certbot/cloudflare.ini
dns_cloudflare_api_token = your_cloudflare_api_tokenchmod 600 ~/.secrets/certbot/cloudflare.iniFix 5: Fix Systemd Timer Not Running
Certbot installed via system packages (apt, dnf) uses a systemd timer. Check if it’s running:
# Check the timer status
sudo systemctl status certbot.timer
# If inactive or failed, enable and start it
sudo systemctl enable certbot.timer
sudo systemctl start certbot.timer
# List all timers — verify certbot is scheduled
sudo systemctl list-timers --all | grep certbotIf the timer doesn’t exist, check for a cron job:
# Check cron jobs
sudo crontab -l
sudo cat /etc/cron.d/certbot
# Manual cron setup (if timer doesn't exist)
sudo crontab -e
# Add:
0 0,12 * * * root certbot renew --quiet --post-hook "systemctl reload nginx"Test the renewal manually:
# Simulate what the timer would do
sudo certbot renew --quiet
# Check the systemd journal for renewal logs
sudo journalctl -u certbot.service --since "7 days ago"Fix 6: Fix Certificate After Renewal (Nginx Reload)
Renewing the certificate doesn’t automatically reload nginx. The old certificate stays in memory until nginx restarts:
# After manual renewal — reload nginx to use the new certificate
sudo nginx -t && sudo systemctl reload nginx
# Verify the new certificate is being served
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | openssl x509 -noout -dates
# notAfter should show the new expiry dateUse a deploy hook to reload nginx automatically after every renewal:
# Create a deploy hook
sudo nano /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh#!/bin/bash
# /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh
nginx -t && systemctl reload nginxsudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.shNow every successful renewal automatically reloads nginx.
Fix 7: Fix Rate Limit Issues
Let’s Encrypt allows 5 duplicate certificates per domain per week. If you hit this:
# Always use --dry-run for testing
certbot renew --dry-run
# Use the staging environment for development testing
certbot certonly --staging -d example.com -d www.example.com --webroot -w /var/www/certbotCheck your current rate limit status:
- Visit
https://crt.sh/?q=example.comto see all certificates issued for your domain. - If you see many recent certificates, wait until the rolling 7-day window passes.
If you’ve hit rate limits and need a certificate urgently:
# Use the staging environment — unlimited issuance, but not trusted by browsers
certbot certonly --staging \
--webroot -w /var/www/certbot \
-d example.com -d www.example.com
# Once rate limits reset, issue a production certificate
certbot certonly \
--webroot -w /var/www/certbot \
-d example.com -d www.example.comFix 8: Update Certbot
Outdated Certbot versions fail when Let’s Encrypt deprecates old API endpoints:
# Check current version
certbot --version
# Ubuntu/Debian — update via snap (recommended)
sudo snap install --classic certbot
sudo snap refresh certbot
# Ubuntu/Debian — update via apt
sudo apt update && sudo apt upgrade certbot python3-certbot-nginx
# CentOS/RHEL — update via dnf/yum
sudo dnf update certbot
# pip installation — update
pip install --upgrade certbot certbot-nginxMigrate from apt to snap (recommended for Ubuntu):
# Remove old apt version
sudo apt remove certbot
# Install via snap
sudo snap install --classic certbot
sudo ln -s /snap/bin/certbot /usr/bin/certbot
# Verify
certbot --versionStill Not Working?
Check the Certbot log for detailed errors:
sudo tail -100 /var/log/letsencrypt/letsencrypt.logVerify the renewal config file is correct:
sudo cat /etc/letsencrypt/renewal/example.com.conf
# Check: authenticator, webroot_path, server (should be acme-v02, not acme-v01)If server in the config still points to acme-v01.api.letsencrypt.org:
# Update all renewal configs to the v02 endpoint
sudo sed -i 's|acme-v01.api.letsencrypt.org/directory|acme-v02.api.letsencrypt.org/directory|g' /etc/letsencrypt/renewal/*.confCheck if Let’s Encrypt itself has an outage: Visit https://letsencrypt.status.io/ — if there’s a known incident, wait and retry.
Delete and reissue if the cert is corrupted:
# Backup first
sudo cp -r /etc/letsencrypt /etc/letsencrypt.bak
# Delete the old cert and reissue
sudo certbot delete --cert-name example.com
sudo certbot certonly --webroot -w /var/www/certbot -d example.com -d www.example.com
sudo systemctl reload nginxFor related SSL and nginx issues, see Fix: Nginx SSL Handshake Failed and Fix: Nginx 502 Bad Gateway.
Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.
Was this article helpful?
Related Articles
Fix: Nginx location Block Not Matching (Wrong Route Served)
How to fix Nginx location blocks not matching — caused by prefix vs regex priority, trailing slash issues, root vs alias confusion, and try_files misconfiguration.
Fix: Nginx SSL: error:0A00006C:SSL routines::bad key / SSL handshake failed
How to fix Nginx SSL handshake failed and certificate errors caused by mismatched keys, wrong certificate chain, expired certs, TLS version issues, and permission problems.
Fix: Linux OOM Killer Killing Processes (Out of Memory)
How to fix Linux OOM killer terminating processes — reading oom_kill logs, adjusting oom_score_adj, adding swap, tuning vm.overcommit, and preventing memory leaks.
Fix: Kubernetes Ingress Not Working (404, 502, or Traffic Not Routing)
How to fix Kubernetes Ingress not routing traffic — why Ingress returns 404 or 502, how to configure annotations correctly, debug ingress-nginx and AWS ALB Ingress Controller, and verify backend service health.