Let's Encrypt certificates expire every 90 days, making automatic renewal critical. When renewal fails silently, your sites go down with SSL errors. This guide covers diagnosing and fixing the most common renewal failure causes.
Check Certificate Status
# Check expiry of all certificates
sudo certbot certificates
# Check specific domain's certificate
echo | openssl s_client -servername example.com -connect example.com:443 2>/dev/null | openssl x509 -noout -dates
# Check when certbot last ran
sudo journalctl -u certbot.timer --since "7 days ago"
sudo journalctl -u certbot --since "7 days ago"
# Check cron/timer
sudo systemctl list-timers | grep certbot
crontab -l | grep certbot
Common Failures and Fixes
1. Port 80 Not Accessible
# HTTP-01 challenge requires port 80 to be open
# Test: can Let's Encrypt reach your server on port 80?
curl -I http://yourdomain.com/.well-known/acme-challenge/test
# Fix: Ensure port 80 is open
sudo ufw allow 80/tcp
sudo iptables -A INPUT -p tcp --dport 80 -j ACCEPT
# Fix: Ensure web server serves the challenge directory
# Nginx:
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
# Fix: If port 80 redirects to 443, the challenge still works
# But ensure the redirect doesn't apply to /.well-known/
2. DNS Not Pointing to Your Server
# The domain must resolve to your server's IP
dig +short A yourdomain.com
dig +short A www.yourdomain.com
# If using Cloudflare proxy, temporarily switch to DNS-only (grey cloud)
# Or use DNS-01 challenge instead
# Fix: Use DNS validation (works behind Cloudflare proxy)
sudo certbot certonly --dns-cloudflare \
--dns-cloudflare-credentials /etc/letsencrypt/cloudflare.ini \
-d yourdomain.com -d *.yourdomain.com
3. Rate Limits
# Let's Encrypt rate limits:
# - 50 certificates per registered domain per week
# - 5 duplicate certificates per week
# - 5 failed validations per account per hostname per hour
# Check rate limit status
# https://crt.sh/?q=yourdomain.com
# Fix: Wait and retry, or use staging for testing
sudo certbot certonly --staging -d yourdomain.com # No rate limits
4. Certbot Configuration Issues
# Check renewal configuration
cat /etc/letsencrypt/renewal/yourdomain.com.conf
# Dry run to test without actually renewing
sudo certbot renew --dry-run
# Force renewal (bypasses "not due" check)
sudo certbot renew --force-renewal
# If certbot is broken, reinstall
sudo apt install --reinstall certbot python3-certbot-nginx
# Delete and recreate certificate
sudo certbot delete --cert-name yourdomain.com
sudo certbot certonly --nginx -d yourdomain.com -d www.yourdomain.com
5. Web Server Not Reloading After Renewal
# Check renewal hooks
ls /etc/letsencrypt/renewal-hooks/deploy/
# Create a deploy hook to reload web server
cat > /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh /dev/null | \
openssl x509 -noout -enddate 2>/dev/null | cut -d= -f2)
if [ -z "$expiry" ]; then
echo "CANNOT CHECK: $domain"
continue
fi
days_left=$(( ( $(date -d "$expiry" +%s) - $(date +%s) ) / 86400 ))
if [ "$days_left" -lt "$WARN_DAYS" ]; then
echo "ALERT: $domain expires in $days_left days!"
else
echo "OK: $domain expires in $days_left days"
fi
done
Best Practices
- Always test with
--dry-runafter configuration changes - Use DNS validation if your server is behind Cloudflare or a CDN
- Set up expiry monitoring independent of certbot — don't rely only on auto-renewal
- Check timer/cron is active:
systemctl list-timers | grep certbot - Keep certbot updated — old versions may fail with new ACME protocol changes
- Use deploy hooks to ensure the web server loads the new certificate