← Back to Blog

Let's Encrypt SSL Renewal Failed: Fix Certbot Errors Before Your Site Goes Down

Your Let's Encrypt certificate just failed to renew, and you probably did not notice. Certbot ran silently in the background, hit an error, and moved on. In 30 days or less, your visitors will see a scary browser warning instead of your website. Let's fix it before that happens.

TL;DR

Run sudo certbot renew --dry-run to see the error. The most common cause is port 80 being blocked by a firewall or another process using it. Fix it by opening port 80 temporarily during renewal, or switch to a DNS challenge so you never need port 80 at all.

Why Let's Encrypt Renewals Fail Silently

Here is the thing most people do not realize about Let's Encrypt: the initial setup works perfectly, you get your shiny green padlock, and then you forget about it. And that is exactly how it is designed to work. Certbot sets up a systemd timer or cron job that runs twice a day and renews your certificate automatically when it is within 30 days of expiring.

The problem is what happens when that automatic renewal fails. Certbot logs the error to /var/log/letsencrypt/letsencrypt.log, and that is it. No email notification. No Slack alert. No blinking red light on your dashboard. It just quietly fails and tries again next time the timer fires. If the underlying issue is not resolved, it keeps failing until your certificate expires and your site goes down.

I have seen production sites go down on a Saturday night because a firewall rule changed three weeks earlier and nobody noticed the renewal errors piling up. Let's Encrypt certificates are only valid for 90 days, and certbot starts trying to renew at 30 days remaining. That gives you a 30-day window to catch the problem. But if you are not looking, that window closes fast.

The good news is that diagnosing and fixing renewal failures is straightforward once you know where to look. Let's walk through it step by step.

Step 1: Check Your Current Certificate Expiry

Before you fix anything, you need to know how much time you have. There are two quick ways to check.

Using certbot

The simplest method if you have certbot installed on the server:

sudo certbot certificates

This shows all your managed certificates, their domains, expiry dates, and file paths. Look for the "Expiry Date" line. If it says something like "VALID: 5 days" you need to act now.

Using openssl from anywhere

You can check any site's certificate expiry remotely without SSH access:

echo | openssl s_client -servername yourdomain.com -connect yourdomain.com:443 2>/dev/null | openssl x509 -noout -dates

This outputs the notBefore and notAfter dates. If notAfter is less than 14 days away, you should treat this as urgent. You can also use our SSL Checker to inspect your certificate chain and expiry without touching the command line at all.

Step 2: Run a Dry Run to See the Error

This is the single most important diagnostic command for certbot issues:

sudo certbot renew --dry-run

The --dry-run flag simulates the entire renewal process without actually changing anything. It contacts Let's Encrypt's staging servers, attempts the challenge, and reports exactly what went wrong. If the dry run succeeds, your real renewal will succeed too.

If it fails, the output will tell you why. Common error messages include:

  • "Could not bind to port 80" - something else is using port 80, or certbot does not have permission
  • "Connection refused" or "Timeout during connect" - firewall is blocking port 80
  • "Unauthorized 403" - the challenge file is not accessible via HTTP
  • "Too many certificates already issued" - you hit a rate limit
  • "The client lacks sufficient authorization" - domain validation failed

Read the error carefully. It almost always tells you exactly what is wrong. Let's go through the most common causes and their fixes.

Step 3: Port 80 Blocked (The Most Common Cause)

By far the most frequent reason for renewal failure is that Let's Encrypt cannot reach your server on port 80. The HTTP-01 challenge works by placing a temporary file on your server and then having Let's Encrypt's validation servers fetch it over plain HTTP on port 80. If anything blocks that request, the challenge fails.

Firewall blocking port 80

Check your firewall rules. On Ubuntu/Debian with UFW:

# Check current rules
sudo ufw status

# If port 80 is not listed, add it
sudo ufw allow 80/tcp

On CentOS/RHEL with firewalld:

sudo firewall-cmd --list-ports
sudo firewall-cmd --permanent --add-port=80/tcp
sudo firewall-cmd --reload

Do not forget to check AWS Security Groups, GCP firewall rules, or Azure NSGs if you are running in the cloud. I have seen plenty of cases where the OS firewall is wide open but the cloud provider's firewall blocks port 80.

Another process using port 80

If you are using the standalone plugin (certbot runs its own temporary web server), another service on port 80 will cause a conflict:

# Find what is using port 80
sudo ss -tlnp | grep :80

If Apache or Nginx is running, you have two options: switch to the webroot plugin (recommended), or stop the web server during renewal. Stopping the web server causes a brief downtime, so the webroot approach is almost always better.

Cloudflare or CDN proxy intercepting

If your domain is behind Cloudflare with the orange cloud (proxy) enabled, Let's Encrypt's validation servers will hit Cloudflare instead of your origin server. Cloudflare may cache or redirect the challenge request, causing it to fail. You have three options:

  • Switch to DNS-01 challenge (best option, covered in Step 5)
  • Temporarily set DNS to grey cloud (DNS only, no proxy) during renewal
  • Use a Cloudflare Origin Certificate instead (covered later in this article)

Step 4: Webroot Challenge Fails

If you used --webroot when you originally got your certificate, certbot places a challenge file in your web root's .well-known/acme-challenge/ directory. Let's Encrypt then fetches that file over HTTP to prove you control the domain. This can fail for several reasons.

Wrong webroot path

Check what webroot path certbot has stored:

sudo cat /etc/letsencrypt/renewal/yourdomain.com.conf | grep webroot

If you changed your web server configuration or moved your document root, the stored path might be wrong. Update it by running:

sudo certbot renew --webroot -w /var/www/html

Replace /var/www/html with your actual document root.

Web server blocking .well-known

Some web server configurations block dotfiles or specific directories. Make sure your Nginx or Apache config allows access to the challenge path:

# Nginx - add this inside your server block
location ^~ /.well-known/acme-challenge/ {
    allow all;
    root /var/www/html;
    try_files $uri =404;
}
# Apache - add to your VirtualHost or .htaccess
<Directory "/var/www/html/.well-known/acme-challenge">
    Options None
    AllowOverride None
    Require all granted
</Directory>

HTTP to HTTPS redirect breaking the challenge

If you have a blanket redirect from HTTP to HTTPS, the challenge file might not be accessible over plain HTTP. Let's Encrypt follows redirects, but only up to 10 times and only if they eventually lead to the challenge token over port 80. The safest approach is to exclude the challenge path from your redirect:

# Nginx - place BEFORE your redirect block
location ^~ /.well-known/acme-challenge/ {
    allow all;
    root /var/www/html;
}

location / {
    return 301 https://$host$request_uri;
}

Step 5: DNS Challenge Setup (The Better Way)

If you are tired of dealing with port 80 issues, the DNS-01 challenge is your answer. Instead of placing a file on your web server, it creates a temporary TXT record in your DNS. This has several advantages:

  • Port 80 does not need to be open at all
  • Works behind CDNs and proxies (Cloudflare, AWS CloudFront)
  • Supports wildcard certificates (*.yourdomain.com)
  • Works for servers that are not publicly accessible

Cloudflare DNS plugin setup

Cloudflare is one of the most popular DNS providers, and certbot has a dedicated plugin for it. Here is how to set it up:

# Install the Cloudflare plugin
sudo apt install python3-certbot-dns-cloudflare    # Debian/Ubuntu
sudo snap install certbot-dns-cloudflare           # If using snap

Create a credentials file with your Cloudflare API token:

# /etc/letsencrypt/cloudflare.ini
dns_cloudflare_api_token = YOUR_CLOUDFLARE_API_TOKEN

Lock down the permissions on that file:

sudo chmod 600 /etc/letsencrypt/cloudflare.ini

Now request your certificate using the DNS challenge:

sudo certbot certonly \
  --dns-cloudflare \
  --dns-cloudflare-credentials /etc/letsencrypt/cloudflare.ini \
  -d yourdomain.com \
  -d "*.yourdomain.com"

That last line gets you a wildcard certificate. You cannot get wildcards with HTTP-01. This is the single biggest reason to switch to DNS validation.

Other DNS providers

Certbot has plugins for Route53 (certbot-dns-route53), DigitalOcean (certbot-dns-digitalocean), Google Cloud DNS (certbot-dns-google), and many others. The setup pattern is the same: install the plugin, provide API credentials, and use --dns-* flags. Check the certbot documentation for the full list.

Step 6: Rate Limits Hit

Let's Encrypt enforces rate limits to prevent abuse. If you have been testing or re-issuing certificates repeatedly, you might have hit one. The main limits are:

  • 50 certificates per registered domain per week - this counts all subdomains under your root domain
  • 5 duplicate certificates per week - same exact set of domain names
  • 5 failed validations per account, per hostname, per hour - repeated failures lock you out temporarily
  • 300 new orders per account per 3 hours

If you see a rate limit error, the solution is usually to wait. The limits reset on a rolling 7-day window. While waiting, fix the underlying issue that caused you to re-issue so many certificates in the first place.

Pro tip: Always use --dry-run (which hits Let's Encrypt staging servers) when testing. Staging has much higher rate limits and will not count against your production quota. This one habit will save you from ever hitting rate limits accidentally.

Step 7: Certbot Version Outdated

If you installed certbot via apt on an older Ubuntu or Debian release, you might be running a version that is years behind. Older versions can have bugs, missing features, and compatibility issues with current Let's Encrypt protocols.

Check your version

certbot --version

If you are running anything below version 2.0, you should upgrade.

Snap is now the recommended install method

The certbot team recommends using snap for installation. It auto-updates and always has the latest version:

# Remove old apt version
sudo apt remove certbot

# Install via snap
sudo snap install --classic certbot

# Create symlink so the command works from PATH
sudo ln -s /snap/bin/certbot /usr/bin/certbot

After upgrading, run sudo certbot renew --dry-run again to confirm everything works with the new version. Your existing certificates and renewal configurations are preserved during the upgrade because they live in /etc/letsencrypt/, which is not touched by the uninstall/reinstall.

Step 8: Apache or Nginx Not Reloading After Renewal

Here is a subtle one. Certbot renews the certificate files on disk, but your web server is still serving the old certificate from memory. Apache and Nginx load certificates into memory at startup and do not automatically pick up file changes. You need a deploy hook to reload the web server after a successful renewal.

# For Nginx
sudo certbot renew --deploy-hook "systemctl reload nginx"

# For Apache
sudo certbot renew --deploy-hook "systemctl reload apache2"

To make this permanent so it runs on every automatic renewal, add the hook to your renewal configuration:

# Edit /etc/letsencrypt/renewal/yourdomain.com.conf
# Add under [renewalparams]:
renew_hook = systemctl reload nginx

Or create a deploy hook script that runs for all certificates:

# /etc/letsencrypt/renewal-hooks/deploy/reload-webserver.sh
#!/bin/bash
systemctl reload nginx
sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-webserver.sh

Any executable script in /etc/letsencrypt/renewal-hooks/deploy/ will run after every successful renewal. This is the cleanest approach if you manage multiple certificates on the same server.

Check Your SSL Certificate Now

Do not wait for renewal to fail. Use SecureBin's SSL Checker to inspect your certificate chain, expiry date, and TLS configuration in seconds.

Check Your SSL Free

Pro Tip: Set Up a Certificate Expiry Monitor

The best fix for renewal failures is catching them before the certificate actually expires. Here is a simple bash script that checks your certificate expiry and sends an alert when it is within 14 days of expiring:

#!/bin/bash
# cert-monitor.sh - Alert on expiring SSL certificates
DOMAIN="yourdomain.com"
DAYS_WARN=14
SLACK_WEBHOOK="https://hooks.slack.com/services/YOUR/WEBHOOK/URL"

EXPIRY=$(echo | openssl s_client -servername "$DOMAIN" \
  -connect "$DOMAIN:443" 2>/dev/null | \
  openssl x509 -noout -enddate | cut -d= -f2)

EXPIRY_EPOCH=$(date -d "$EXPIRY" +%s 2>/dev/null || date -jf "%b %d %T %Y %Z" "$EXPIRY" +%s)
NOW_EPOCH=$(date +%s)
DAYS_LEFT=$(( (EXPIRY_EPOCH - NOW_EPOCH) / 86400 ))

if [ "$DAYS_LEFT" -lt "$DAYS_WARN" ]; then
  MSG="WARNING: SSL certificate for $DOMAIN expires in $DAYS_LEFT days ($EXPIRY)"
  echo "$MSG"
  # Send to Slack
  curl -s -X POST -H 'Content-type: application/json' \
    --data "{\"text\":\"$MSG\"}" "$SLACK_WEBHOOK"
  # Or send email
  # echo "$MSG" | mail -s "SSL Cert Expiring: $DOMAIN" admin@yourdomain.com
fi

Add this to your crontab to run daily:

# Check certificate expiry every day at 9 AM
0 9 * * * /opt/scripts/cert-monitor.sh

This gives you a 14-day heads up before things go wrong. Combine this with the certbot dry run, and you will never be surprised by an expired certificate again.

ACME Challenge Types Compared

Let's Encrypt uses the ACME protocol with three different challenge types. Here is when to use each one:

Challenge How It Works Pros Cons Best For
HTTP-01 Places a file in .well-known/acme-challenge/ and validates over port 80 Simple setup, works out of the box with most web servers Requires port 80 open, does not support wildcards, fails behind CDN proxies Simple single-domain setups with direct public access
DNS-01 Creates a TXT record at _acme-challenge.yourdomain.com No port 80 needed, supports wildcards, works behind CDNs and proxies Requires DNS API access, slightly more complex setup, DNS propagation delays Wildcard certs, Cloudflare/CDN users, internal servers
TLS-ALPN-01 Uses a self-signed cert with a special ALPN extension on port 443 Only needs port 443, no port 80 Limited tooling support, not widely used, does not support wildcards Environments where only port 443 is available

For most people, HTTP-01 is fine if you have a simple setup with port 80 open. If you are behind Cloudflare, use a CDN, need wildcards, or want the most reliable option, go with DNS-01.

Cloudflare Origin Certificates: The Zero-Renewal Alternative

If your site is behind Cloudflare (and honestly, most sites should be), there is an option that sidesteps the entire Let's Encrypt renewal problem: Cloudflare Origin Certificates.

A Cloudflare Origin Certificate is a free SSL certificate issued by Cloudflare that you install on your origin server. It is valid for up to 15 years. That is not a typo. Fifteen years. No renewal, no certbot, no cron jobs, no port 80 headaches.

The catch is that this certificate is only trusted by Cloudflare. If someone hits your origin IP directly (bypassing Cloudflare), they will see an untrusted certificate warning. But if all your traffic flows through Cloudflare, that does not matter. Your visitors always connect to Cloudflare (which has its own trusted certificate), and Cloudflare connects to your origin using the Origin Certificate.

To set it up, go to the Cloudflare dashboard, navigate to SSL/TLS, then Origin Server, and click "Create Certificate." Download the certificate and private key, install them in your web server, and set Cloudflare's SSL mode to "Full (Strict)." Done. No more renewal failures.

For a deeper look at SSL configuration best practices, check out our SSL certificate security checklist.

Monitoring and Alerting Setup

Beyond the bash script above, there are several ways to monitor certificate health:

  • Uptime monitoring services (UptimeRobot, Better Stack, Pingdom) all offer SSL expiry alerts as part of their free tiers
  • Certbot's built-in notification: Let's Encrypt sends email warnings at 20 days before expiry to the address you registered with. Make sure that email address is monitored
  • Prometheus + blackbox_exporter: If you already run Prometheus, the blackbox exporter can probe HTTPS endpoints and expose probe_ssl_earliest_cert_expiry as a metric. Set an alert at 14 days
  • Our SSL Checker: Bookmark it and check your domains periodically, or use the Exposure Checker which includes SSL expiry as one of its 19 security checks

The goal is redundancy. Your certbot timer should handle renewal automatically. But if it fails, you want at least one independent system that will yell at you before the certificate expires. If your SSL chain configuration is causing issues beyond expiry, read our guide on fixing incomplete SSL certificate chains.

Common Mistakes That Break Renewal

After helping teams debug hundreds of renewal failures, these are the six mistakes I see over and over:

  1. Not opening port 80 - You set up HTTPS, redirected everything, and then closed port 80 in the firewall. Certbot needs it for HTTP-01 challenges. Either keep it open or switch to DNS-01
  2. Wrong webroot path in renewal config - You migrated your site to a new directory but certbot still points to the old one. Check /etc/letsencrypt/renewal/yourdomain.com.conf
  3. Expired or revoked ACME account key - Rare, but it happens. If your account key is compromised or the registration expired, re-register with certbot register --agree-tos -m your@email.com
  4. Never testing with --dry-run - If you never run a dry run, you will not know about problems until the certificate actually expires. Run it monthly at minimum
  5. Relying solely on the snap timer or systemd timer - The timer runs certbot, but it does not alert you on failure. Add your own monitoring on top
  6. Multiple certbot installations fighting each other - If you installed certbot via both apt and snap, you might have two different versions managing the same certificates. Pick one and remove the other

Frequently Asked Questions

How often does Let's Encrypt renew certificates?

Let's Encrypt certificates are valid for 90 days. Certbot attempts renewal when the certificate has 30 days or less remaining. The certbot systemd timer or cron job typically runs twice daily, but it only actually renews when the certificate is within 30 days of expiry. This means you get roughly 60 attempts over 30 days. If all of them fail, your certificate expires.

Can I use Let's Encrypt behind Cloudflare?

Yes, but HTTP-01 challenges will not work if Cloudflare is proxying traffic (orange cloud) because Cloudflare intercepts the request. Use DNS-01 challenge with the Cloudflare DNS plugin instead, or temporarily pause Cloudflare during renewal. Alternatively, use a Cloudflare Origin Certificate which is valid for up to 15 years and requires no renewal at all.

What happens if my Let's Encrypt certificate expires?

Browsers will show a full-page security warning (ERR_CERT_DATE_INVALID or SEC_ERROR_EXPIRED_CERTIFICATE) that blocks visitors from reaching your site. Search engines will also flag your site, potentially dropping your rankings. Most visitors will leave immediately. The good news is that you can still renew an expired certificate using the same certbot renew command. Expiration does not prevent renewal. Fix the underlying issue, run the renewal, and your site is back.

Is Your Site Exposed?

SSL is just one piece of the security puzzle. Run a full exposure check to find missing headers, exposed files, DNS issues, and more.

Run Exposure Check Free

The Bottom Line

Let's Encrypt renewal failures are almost always caused by one of a handful of issues: port 80 blocked, wrong webroot path, CDN interference, or an outdated certbot version. The fix is usually a five-minute task. The real danger is not the failure itself, it is not knowing about it until your site is down.

Start with sudo certbot renew --dry-run. If it passes, you are good. If it fails, walk through the steps above. Set up monitoring so you never get surprised again. And if port 80 is going to be a recurring headache, switch to DNS challenges or Cloudflare Origin Certificates and eliminate the problem entirely.

Related tools: SSL Checker, Exposure Checker, DNS Lookup, Certificate Decoder, Cron Parser, and 70+ more free tools.

Related guides: Fix Incomplete SSL Certificate Chains, SSL Certificate Security Checklist.

UK
Written by Usman Khan
DevOps Engineer | MSc Cybersecurity | CEH | AWS Solutions Architect

Usman has 10+ years of experience securing enterprise infrastructure, managing high-traffic servers, and building zero-knowledge security tools. Read more about the author.