Back to tutorials
Tutorial

Let’s Encrypt Renewal Troubleshooting Tutorial (2026): Fix Auto-Renew Failures on Nginx, Apache, and cPanel

Let’s Encrypt renewal troubleshooting tutorial for 2026: fix failed renewals on Nginx, Apache, and cPanel fast.

By Anurag Singh
Updated on Sep 24, 2026
Category: Tutorial
Share article
Let’s Encrypt Renewal Troubleshooting Tutorial (2026): Fix Auto-Renew Failures on Nginx, Apache, and cPanel

Your certificate didn’t “suddenly expire.” Renewals were failing quietly for days (sometimes weeks). Browsers only forced you to notice.

This Let’s Encrypt renewal troubleshooting tutorial gives you a clean set of checks. You’ll pinpoint why auto-renew broke on a VPS, dedicated box, or cPanel host. You’ll fix it without trial-and-error.

The steps below assume Ubuntu/Debian (common on VPS hosting) with Certbot, another ACME client, or cPanel AutoSSL. The same workflow applies to AlmaLinux/Rocky. A few paths and service names differ.

Before you change anything: confirm what’s actually expiring

Pin down three things first: the failing hostname, the certificate file your server is serving, and the system responsible for renewals.

  • Browser warning domain: write down the exact FQDN (www vs apex) and the expiry date shown.
  • Server-side certificate path: confirm which certificate files your web server references right now.
  • Issuer: is it Let’s Encrypt, cPanel, or another CA? Don’t troubleshoot the wrong toolchain.

On Nginx, locate the active cert paths:

sudo nginx -T | grep -R --line-number "ssl_certificate" -n /etc/nginx 2>/dev/null
sudo nginx -T | grep -R --line-number "ssl_certificate_key" -n /etc/nginx 2>/dev/null

On Apache:

sudo apachectl -S 2>/dev/null | head
sudo grep -R --line-number -E "SSLCertificate(File|KeyFile|ChainFile)" /etc/apache2 /etc/httpd 2>/dev/null

Now check expiry for the certificate file referenced by your config:

sudo openssl x509 -in /etc/letsencrypt/live/example.com/fullchain.pem -noout -issuer -subject -dates

If the configured path isn’t under /etc/letsencrypt/, you’re probably using a control panel store or a manually installed cert.

Document that first. Your renewal fix depends on it.

Let’s Encrypt renewal troubleshooting tutorial: map your ACME method in 60 seconds

Renewal failures vary by validation method. Identify yours before you change configs.

  • HTTP-01: the CA requests http://yourdomain/.well-known/acme-challenge/… on port 80.
  • DNS-01: you publish a TXT record; inbound HTTP isn’t required (also required for wildcard certs).
  • TLS-ALPN-01: the CA validates over port 443 with ALPN; less common in typical hosting setups.

On a Certbot-based server, list what Certbot thinks it owns:

sudo certbot certificates

Then open the renewal config for a failing domain. It often shows the root cause.

sudo ls -1 /etc/letsencrypt/renewal/
sudo sed -n '1,140p' /etc/letsencrypt/renewal/example.com.conf

Look for lines like authenticator = webroot, authenticator = nginx, or authenticator = dns-*.

If you’re on cPanel/WHM, AutoSSL usually handles renewals. Troubleshooting happens in WHM plus DNS, firewall, and routing checks.

If your DNS zones live on separate nameservers (common with clusters), keep that in mind. See cPanel DNS cluster setup tutorial.

Step 1 — Reproduce the failure safely (dry-run) and capture logs

Don’t wait for the next scheduled run. Trigger a renewal attempt now. Save the exact error output.

Certbot dry-run (recommended):

sudo certbot renew --dry-run

Real renewal (only after the dry-run makes sense):

sudo certbot renew

On systemd-based distros, check timers and recent service output:

sudo systemctl list-timers | grep -E "certbot|acme" || true
sudo systemctl status certbot.timer 2>/dev/null || true
sudo journalctl -u certbot.service --no-pager -n 200 2>/dev/null || true

If you use another ACME client (acme.sh, dehydrated), find its cron/systemd logs. Capture the same detail.

The goal is simple: reproduce the failure and read the error string. Don’t guess.

Step 2 — Fix the most common cause: HTTP-01 can’t reach your server on port 80

HTTP-01 requires public access to port 80. Redirects to HTTPS are fine. Validation still starts on plain HTTP.

Quick diagnostics (works on any VPS)

  1. Verify DNS points to the correct server IP.
  2. Verify port 80 is open and reachable from outside your network.
  3. Verify your web server serves /.well-known/acme-challenge/ without interference.

Check DNS A/AAAA from the server:

getent ahosts example.com
getent ahosts www.example.com

If you recently migrated, renewals often fail because validation hits the old server.

Low TTL plus a controlled cutover prevents that. Use the same approach described in DNS cutover tutorial.

Confirm firewall. On Ubuntu/Debian with UFW:

sudo ufw status verbose

Make sure 80/tcp is allowed:

sudo ufw allow 80/tcp
sudo ufw reload

If you run both a cloud firewall and a host firewall, check both layers.

If you want a safe baseline for SSH access while editing rules, follow VPS firewall setup guide tutorial.

Test external reachability (from your laptop):

curl -I http://example.com/.well-known/acme-challenge/ping

A 404 is fine. Timeouts, connection refused, unexpected 30x loops, or landing on the wrong server are the real problems.

Fix: preserve ACME challenge path during redirects

A common pattern: a global HTTP→HTTPS redirect or app-level rewrite swallows the challenge request.

Nginx (recommended snippet inside your port 80 server block):

server {
  listen 80;
  server_name example.com www.example.com;

  location ^~ /.well-known/acme-challenge/ {
    root /var/www/letsencrypt;
    default_type "text/plain";
    try_files $uri =404;
  }

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

Create the webroot and a test file:

sudo mkdir -p /var/www/letsencrypt/.well-known/acme-challenge
echo ok | sudo tee /var/www/letsencrypt/.well-known/acme-challenge/health

Reload Nginx safely:

sudo nginx -t && sudo systemctl reload nginx

Verify:

curl -s http://example.com/.well-known/acme-challenge/health

Apache (simple approach with an Alias):

sudo mkdir -p /var/www/letsencrypt/.well-known/acme-challenge
sudo tee /etc/apache2/conf-available/letsencrypt-acme.conf >/dev/null <<'EOF'
Alias /.well-known/acme-challenge/ "/var/www/letsencrypt/.well-known/acme-challenge/"
<Directory "/var/www/letsencrypt/.well-known/acme-challenge/">
  AllowOverride None
  Options None
  Require all granted
</Directory>
EOF
sudo a2enconf letsencrypt-acme
sudo apachectl configtest && sudo systemctl reload apache2

Step 3 — Fix webroot and permission problems (the “unauthorized” trap)

If you use authenticator = webroot, Certbot writes challenge files into a specific directory.

Renewals fail when that directory moved, was deleted, or no longer matches the vhost serving the domain.

Pull the configured webroot from the renewal file:

sudo awk -F' = ' '/webroot_path/ {print}' /etc/letsencrypt/renewal/example.com.conf

Confirm the directory exists. Then confirm the web server can traverse the full path.

sudo ls -ld /var/www/example.com
sudo namei -l /var/www/example.com/.well-known/acme-challenge 2>/dev/null || true

One hosting gotcha: frameworks and CMSes that route every request through the app (WordPress, Laravel) can block the challenge file. The server never serves it as a static file.

Using the dedicated ACME location from the previous section avoids this. It keeps challenge traffic out of your app stack.

Then re-test the renewal flow:

sudo certbot renew --dry-run

Step 4 — “Too many redirects” and “redirect loop” failures

Let’s Encrypt follows redirects. Redirect loops end validation fast.

The usual culprits are CDN settings, canonical-host rules, or the origin using the wrong scheme.

  • a CDN/proxy forces HTTPS while the origin forces something else
  • your application forces a canonical host (www or non-www) inconsistently
  • port 80 redirects to port 443, and 443 redirects back due to wrong scheme headers

Trace the redirect chain:

curl -I http://example.com/.well-known/acme-challenge/test -L

If you see the same Location targets repeating, fix redirects so the ACME path is excluded on port 80.

Keep ACME validation fixes separate from general HTTPS cleanup. For app-level loops during moves or proxy changes, this guide is useful: HTTPS redirect troubleshooting tutorial.

Step 5 — DNS problems: wrong A/AAAA, stale records, and “NXDOMAIN”

Renewals often break after migrations because the domain still points to the old server. IPv6 can also point somewhere else entirely.

Check both A and AAAA using multiple resolvers:

dig +short A example.com @1.1.1.1
dig +short AAAA example.com @1.1.1.1

dig +short A example.com @8.8.8.8
dig +short AAAA example.com @8.8.8.8

If AAAA is wrong, Let’s Encrypt may try IPv6 first and fail.

Either fix IPv6 routing on the server or remove the AAAA record until it’s correct.

Also confirm your authoritative DNS publishes what you think it does.

If you’re using Cloudflare, double-check record content and proxy status. This walkthrough covers the common failure points: Cloudflare DNS setup guide tutorial.

Step 6 — cPanel AutoSSL renewals: what to check in WHM

On cPanel/WHM, AutoSSL is usually straightforward. It gets messy when validation fails or installs vary across services.

Checklist inside WHM (practical order)

  • WHM → SSL/TLS Status: confirm the domain is eligible and not excluded.
  • WHM → Manage AutoSSL: confirm the provider (Let’s Encrypt/other) and verify AutoSSL runs on schedule.
  • WHM → Service Configuration → Service Manager: confirm Apache/Nginx proxy (if used) is running.
  • Firewall: inbound 80/443 must be reachable for validation.
  • DNS: A/AAAA must point to this server.

If your server is noisy (brute-force attempts, spam bursts, repeated 500s), it’s easy to miss the useful AutoSSL errors.

Set up log monitoring so SSL failures stand out. This guide pairs well with AutoSSL triage: cPanel log monitoring tutorial.

Step 7 — Time drift: the silent cause behind weird TLS and ACME failures

If the system clock is off, TLS can fail in ways that look random.

You may also see cron and logs appear “out of order.” That makes renewal debugging miserable.

Check time sync:

timedatectl
chronyc tracking 2>/dev/null || true

If you suspect drift, fix time sync before you keep digging.

This guide focuses on the hosting symptoms you’ll actually notice (SSL errors, cron oddities): VPS time sync troubleshooting tutorial.

Step 8 — Rate limits and duplicate certificates: clean up and reduce churn

If renewals have been failing for a while, you may have multiple duplicate orders.

Let’s Encrypt rate limits are easy to hit if you keep re-issuing before validation works.

Common signs:

  • Errors like too many certificates already issued or rateLimited
  • multiple similar certificate entries in certbot certificates

Use this order instead of repeatedly trying issuance:

  1. Fix validation reachability (DNS/ports/webroot).
  2. Run certbot renew --dry-run to confirm the validation path.
  3. Only then run a real renew.

If you truly need to re-issue, reduce churn by consolidating SANs where it makes sense.

Avoid separate certs for www and apex on the same vhost unless you have a reason.

Step 9 — Deploy hook problems: renewal succeeded, but the site still serves the old cert

This is a classic time sink: Certbot renews successfully, but Nginx/Apache keeps serving the old certificate.

Most of the time it’s a missing reload. Another common cause is renewing one vhost while traffic hits another.

Verify the live certificate over the network

From any machine (or the server itself), check what the endpoint is actually presenting:

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

If the dates are still old, reload the web server:

sudo systemctl reload nginx 2>/dev/null || true
sudo systemctl reload apache2 2>/dev/null || true
sudo systemctl reload httpd 2>/dev/null || true

Make reload automatic (Certbot deploy hook)

Use a deploy hook so services reload only after a successful renewal.

sudo install -d /etc/letsencrypt/renewal-hooks/deploy
sudo tee /etc/letsencrypt/renewal-hooks/deploy/reload-webserver.sh >/dev/null <<'EOF'
#!/bin/sh
set -eu

# Reload only if the service exists; avoids failures on servers that don't use both.
if systemctl is-active --quiet nginx 2>/dev/null; then
  systemctl reload nginx
fi
if systemctl is-active --quiet apache2 2>/dev/null; then
  systemctl reload apache2
fi
if systemctl is-active --quiet httpd 2>/dev/null; then
  systemctl reload httpd
fi
EOF
sudo chmod 0755 /etc/letsencrypt/renewal-hooks/deploy/reload-webserver.sh

Confirm the hook runs cleanly:

sudo certbot renew --dry-run

Step 10 — Prefer DNS-01 when HTTP-01 is fragile (wildcards, locked-down ports, or multi-origin setups)

If any of these describe your setup, DNS-01 is often the calmer option:

  • wildcard certificates (*.example.com)
  • port 80 blocked by policy
  • multiple frontends (load balancers, reverse proxies, CDN) where HTTP-01 gets complicated

The tradeoff is real. You must automate DNS TXT updates through your DNS provider’s API. Otherwise renewals become a recurring manual task.

For production sites, that automation is usually worth the effort.

Operational checklist: keep renewal failures from surprising you again

  • Alert on expiry: monitor certificate dates with uptime monitoring or a cron check.
  • Keep port 80 reachable (or switch to DNS-01). Don’t “clean up” the HTTP listener without a plan.
  • Pin down redirects: exclude /.well-known/acme-challenge/ from rewrite rules.
  • After renewals, reload services via deploy hooks.
  • During migrations: plan DNS cutover so validation reaches the correct origin.

Summary: the fastest path to a working renewal

If you want the short path, run certbot renew --dry-run. Then fix port 80 reachability.

Next, exclude the ACME path from redirects and confirm DNS A/AAAA correctness. Finally, add a deploy hook to reload Nginx/Apache. That combination resolves most real-world renewals that fail on hosting infrastructure.

If you’d rather not keep certificate tooling on your critical path, a managed platform can remove a lot of the operational churn. A managed VPS hosting plan from HostMyCode keeps updates, renewals, and service reload workflows predictable. If you prefer full control, start with a clean HostMyCode VPS, set up renewals once, and monitor them like any other production dependency.

If SSL renewals keep turning into incidents, put the site on infrastructure that supports steady day-to-day operations. HostMyCode offers VPS hosting for hands-on admins and managed VPS hosting when you want renewals, patching, and service reliability handled with less noise.

FAQ: quick answers for common renewal failures

Do I really need port 80 open for Let’s Encrypt?

If you use HTTP-01 validation, yes. Let’s Encrypt must fetch the challenge over plain HTTP. If you can’t open 80, switch to DNS-01.

Why does renewal fail only for the “www” hostname?

Usually DNS mismatch. example.com points to the new server, but www.example.com still points to the old IP (or has a wrong AAAA record). Verify both A and AAAA for both names.

Certbot says renewal succeeded, but browsers still show the old certificate. Why?

The web server likely didn’t reload, or a different vhost is serving the request. Confirm the live cert with openssl s_client, then reload Nginx/Apache and re-check.

What’s the safest way to test changes without breaking live traffic?

Use certbot renew --dry-run. For routing changes, add only an ACME exception path on port 80. Reload the web server after nginx -t or apachectl configtest.

Should I use a wildcard certificate for everything?

Only if it matches your setup. Wildcards require DNS-01, and you need reliable DNS API automation. For single-site hosting, separate SAN certs are often simpler.

Let’s Encrypt Renewal Troubleshooting Tutorial (2026): Fix Auto-Renew Failures on Nginx, Apache, and cPanel | HostMyCode