Skip to content

Fix: Certbot Certificate Renewal Failed (Let's Encrypt)

FixDevs ·

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.com

Or 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 a return 301 redirect, 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 renew is 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-run and 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 --verbose

Check 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.pem

Fix 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/test

If 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.com

If 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 nginx

Automate 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 nginx
chmod +x /etc/letsencrypt/renewal-hooks/pre/stop-nginx.sh
chmod +x /etc/letsencrypt/renewal-hooks/post/start-nginx.sh

Check 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 Group

Fix 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/test

Check the renewal configuration file to verify webroot path:

cat /etc/letsencrypt/renewal/example.com.conf
# Look for:
# authenticator = webroot
# webroot_path = /var/www/certbot

If 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 IP

For 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_token
chmod 600 ~/.secrets/certbot/cloudflare.ini

Fix 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 certbot

If 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 date

Use 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 nginx
sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh

Now 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/certbot

Check your current rate limit status:

  • Visit https://crt.sh/?q=example.com to 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.com

Fix 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-nginx

Migrate 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 --version

Still Not Working?

Check the Certbot log for detailed errors:

sudo tail -100 /var/log/letsencrypt/letsencrypt.log

Verify 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/*.conf

Check 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 nginx

For related SSL and nginx issues, see Fix: Nginx SSL Handshake Failed and Fix: Nginx 502 Bad Gateway.

F

FixDevs

Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.

Was this article helpful?

Related Articles