
An SSL renewal failure is almost never “random.” In real hosting stacks, it usually comes from one of three causes: DNS points to the wrong server, ports 80/443 aren’t reachable (or hit the wrong service), or the ACME challenge is answered by the wrong virtual host.
This SSL renewal troubleshooting tutorial gives you a repeatable workflow for a Linux VPS (Certbot) and cPanel/WHM (AutoSSL). You’ll reproduce the error, fix what breaks validation, and confirm renewals stay healthy.
What you’ll fix (and what you need before you start)
By the end you should be able to:
- Pin the failure on DNS, firewall/routing, web server vhost behavior, rate limiting, or the validation type (HTTP-01 vs DNS-01).
- Run a safe test renewal and read the precise validation error.
- Fix common breakpoints: redirects, reverse proxies, IPv6 gotchas, and cPanel AutoSSL edge cases.
Prereqs: root or sudo access on a VPS, or WHM root access on a cPanel server.
You should also know which hostnames fail. Confirm whether they sit behind a proxy/CDN.
If you’re building or moving servers, plan the cutover first. Pair this with DNS TTL reduction for a safe hosting cutover so you’re not troubleshooting renewals against yesterday’s IP.
Step 1: Confirm DNS hits the server you’re renewing on
Let’s Encrypt validates against public DNS, not your browser cache. If your A/AAAA records point elsewhere, renewals fail even if the site “loads for me.”
-
Check the current A and AAAA records:
dig +short A example.com dig +short AAAA example.com -
Confirm the server’s public IP(s):
curl -4 https://ifconfig.me curl -6 https://ifconfig.me
Fix: update DNS so example.com (and www, plus any SANs) matches the correct server IP. If you host DNS with HostMyCode, manage records from HostMyCode domains and DNS.
Pitfall: AAAA records.
If you publish IPv6 but the server can’t serve the site correctly over IPv6, Let’s Encrypt may validate over IPv6 and fail. Either fix IPv6 routing and web server behavior, or remove the AAAA record for that hostname.
Step 2: Identify which validation method you’re using (HTTP-01 vs DNS-01)
Most VPS and cPanel setups use HTTP-01. With HTTP-01, a file under /.well-known/acme-challenge/ must be served over port 80.
If port 80 is blocked or routed incorrectly, you’ll usually see connection refused, timeout, or 404.
Use DNS-01 when:
- You can’t expose port 80 (locked-down environments),
- You need a wildcard certificate (
*.example.com), - Your app stack makes HTTP-01 routing hard to keep predictable.
Start with HTTP-01 because it’s the quickest to diagnose on typical hosting.
If it stays messy, switch to DNS-01 as the clean fallback.
Step 3: Reproduce the failure with a clean, verbose renewal run (VPS / Certbot)
On Ubuntu/Debian VPS systems, Certbot is commonly installed via apt or snap.
First, confirm which binary you’re actually running:
command -v certbot
certbot --version || true
snap list certbot 2>/dev/null || true
Then run a dry-run renewal. It reproduces the validation flow without burning production rate limits:
sudo certbot renew --dry-run -v
If only one hostname is failing, simulate issuance against that specific webroot. Do this after a --dry-run pass (it remains safe):
sudo certbot certonly --dry-run --webroot -w /var/www/example.com/public_html -d example.com -d www.example.com -v
Where to look:
- Certbot logs:
/var/log/letsencrypt/letsencrypt.log - Deployed certs:
/etc/letsencrypt/live/and/etc/letsencrypt/renewal/
Step 4: Fix port 80/443 reachability and routing issues
HTTP-01 needs inbound port 80 from the public internet.
Start with local checks to catch obvious mistakes. These don’t prove inbound access, but they show what’s listening:
sudo ss -lntp | egrep ':(80|443)\s'
If nothing listens on 80, Let’s Encrypt can’t complete HTTP-01.
Confirm your web service is up:
sudo systemctl status nginx --no-pager || true
sudo systemctl status apache2 --no-pager || true
Next, inspect firewall rules. On Ubuntu, UFW is common:
sudo ufw status verbose
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
If you’re using raw nftables/iptables, confirm you’re accepting those ports.
Quick snapshot:
sudo nft list ruleset | sed -n '1,200p'
If UFW “hardening” is the suspect, follow UFW firewall troubleshooting for SSL renewals. It focuses on the exact hosting failures that break ACME challenges.
Reverse proxy pitfall: if you run Nginx in front of Apache, port 80 must hit Nginx. Nginx must also serve the challenge path correctly.
If you’re not sure which layer is the “front door,” review Reverse proxy setup: Nginx in front of Apache. Get routing stable before you retry renewals.
Step 5: Verify the ACME challenge path serves correctly
Before you rerun Certbot, prove your web server can serve /.well-known/acme-challenge/ over plain HTTP.
-
Create a test file:
sudo mkdir -p /var/www/example.com/public_html/.well-known/acme-challenge echo ok | sudo tee /var/www/example.com/public_html/.well-known/acme-challenge/healthcheck.txt -
Fetch it over HTTP from an external network (your laptop or an online curl shell):
curl -i http://example.com/.well-known/acme-challenge/healthcheck.txt
Expected: 200 with body ok. A 301/302 to HTTPS is usually fine.
If you get 404, 403, or content from a different site, you likely have a vhost/root mismatch.
Step 6: Fix the two most common web server misconfigurations
Most renewal breakage after migrations or panel changes comes back to one of these.
Case A: Wrong document root / vhost catches the request
If http://example.com/.well-known/... serves the default vhost (or another site), Certbot may write the challenge file to a directory your vhost never serves.
- Nginx: verify the correct
server_nameandrootin/etc/nginx/sites-enabled/. - Apache: verify
ServerName,ServerAlias, andDocumentRootin/etc/apache2/sites-enabled/.
Quick diagnostics:
sudo nginx -T 2>/dev/null | sed -n '1,200p'
sudo apachectl -S
Case B: A redirect or rewrite blocks the challenge path
Strict redirects to app routes, or rewrites that deny “dot” directories, can break ACME.
Fix it by explicitly allowing the challenge path.
Nginx snippet (inside the relevant server block):
location ^~ /.well-known/acme-challenge/ {
default_type "text/plain";
root /var/www/example.com/public_html;
allow all;
}
Apache snippet (vhost or .htaccess):
<Directory "/var/www/example.com/public_html/.well-known">
AllowOverride None
Require all granted
</Directory>
Reload after changes:
sudo systemctl reload nginx || sudo systemctl reload apache2
Step 7: cPanel/WHM AutoSSL renewal troubleshooting (practical workflow)
On shared hosting or reseller servers, AutoSSL handles renewals for you. When it fails, focus on two things: the right log and the correct DCV method.
Your goal is to identify which domains fail and why, without guessing across accounts.
-
In WHM, go to SSL/TLS > Manage AutoSSL. Select the provider (commonly Let’s Encrypt in 2026 environments where enabled).
Run an AutoSSL check, then open Check “log” for the exact failure.
-
From CLI (root), tail cPanel logs while you run the check in WHM:
sudo tail -n 200 /usr/local/cpanel/logs/error_log sudo tail -n 200 /usr/local/cpanel/logs/autossl.log 2>/dev/null || true
Most common cPanel causes:
- DCV fails because DNS is wrong: old A record, parked domain pointing elsewhere, or stale IPv6.
- Proxy/CDN mismatch: Cloud proxy hides origin, blocks HTTP-01, or forces HTTPS incorrectly.
- Account-level redirects: app frameworks or WordPress plugins redirect the challenge path.
If the AutoSSL output feels noisy, or you’re chasing failures across many accounts, this companion guide helps you map the log messages to real fixes: cPanel AutoSSL troubleshooting.
Step 8: Handle “mixed hosting” setups: CDN/proxy, NAT, and multiple web servers
Real-world stacks are rarely “just Apache” or “just Nginx.” These setups need one extra sanity check before you keep retrying renewals.
CDN or proxy (Cloudflare-style)
- Temporarily disable proxying for the failing hostname (set DNS record to “DNS only”), renew, then re-enable.
- Confirm that
http://example.com/.well-known/acme-challenge/testreaches your origin and not a cached error page.
NAT or port mapping
If your server sits behind a firewall that forwards ports, Let’s Encrypt must reach the public IP.
That traffic must forward to the correct internal host.
Confirm the forward for port 80 and 443. Also confirm no other host answers those ports.
Apache + Nginx + control panel combo
If a control panel regenerates vhosts, don’t patch random files that will be overwritten.
Use the panel’s supported include paths (for example, Nginx include dirs if provided, or cPanel’s Apache include system). That way, ACME handling survives rebuilds.
Step 9: Rate limits, duplicate certs, and “too many requests” errors
Let’s Encrypt rate limits usually show up after you’ve retried the wrong fix several times.
The pattern is predictable: validation fails, you keep issuing, and retries pile up.
What to do:
- Use
--dry-runwhile debugging. - Don’t issue a fresh cert on every attempt. Fix routing first.
- If you need repeated tests, switch to Let’s Encrypt staging endpoints for the debug cycle (Certbot supports this).
Certbot staging example:
sudo certbot certonly --staging --webroot -w /var/www/example.com/public_html -d example.com -d www.example.com -v
Step 10: If HTTP-01 keeps failing, switch to DNS-01 (clean fallback)
DNS-01 avoids port 80 completely. It also works if your web stack is locked down.
The tradeoff is operational. You need DNS API access, or you’ll update TXT records manually.
At a high level:
- Request a cert using DNS-01.
- Create a TXT record named
_acme-challenge.example.comwith the value Certbot provides. - Wait for DNS propagation, then continue.
If you host DNS at HostMyCode, keep zone management centralized under HostMyCode domains and DNS. That makes TXT updates faster during maintenance windows.
Step 11: Verify the installed certificate and the renewal schedule
Don’t stop at “Certbot succeeded.” Confirm the running web server picked up the new certificate.
Also confirm your renewal job will run again.
Check the live certificate details
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | openssl x509 -noout -issuer -subject -dates
Expected: the notAfter date should be ~90 days out.
Confirm renewal timer/cron
On systemd-based VPS systems:
systemctl list-timers | grep -i certbot || true
systemctl status certbot.timer --no-pager 2>/dev/null || true
Also check cron as a fallback (some environments still use it):
sudo crontab -l | sed -n '1,200p'
ls -la /etc/cron.d | sed -n '1,200p'
Step 12: Quick checklist you can reuse during migrations
Use this before and after moving sites to a new VPS or a new cPanel server:
- DNS A/AAAA records updated and verified from multiple resolvers.
- Port 80 open inbound and routed to the right web server.
/.well-known/acme-challenge/serves from the correct vhost/root.- No app redirect blocks the challenge path (WordPress security plugins are frequent culprits).
- AutoSSL/Certbot dry-run passes before you walk away.
If SSL failures started right after a move, follow a disciplined cutover process too.
This pairs well with server migration tutorial with rsync, DNS cutover, and rollback. Clean migrations prevent the “mystery renewal” problems that show up weeks later.
Where HostMyCode fits (picking the right hosting for reliable renewals)
Renewal issues tend to cluster on stacks that are hard to reason about. Common triggers include overloaded servers, surprise proxies, and “which service owns port 80?” confusion.
If you want a straightforward Linux environment you control end-to-end, start with a HostMyCode VPS and keep your web and firewall rules simple.
If you don’t want to babysit OS updates, firewall changes, and service restarts, managed VPS hosting is the practical option. You still get VPS performance, but certificate renewals and service health stop eating your time.
If renewals keep turning into incidents, the fix is usually ownership and predictability: DNS that points where you think it points, ports that route where you think they route, and a web server config that answers challenges consistently. HostMyCode can help you build that on a HostMyCode VPS, or take the operations load off your team with managed VPS hosting.
FAQ
Why does Let’s Encrypt renewal fail even though HTTPS still works?
Your existing certificate stays valid until it expires. Renewal fails because validation can’t complete (DNS mismatch, blocked port 80, wrong vhost), not because TLS is broken today.
Do I need port 80 open for Let’s Encrypt?
For HTTP-01, yes. If you can’t open port 80, use DNS-01 validation (TXT records) instead.
What’s the fastest way to spot an IPv6-related renewal failure?
Check whether an AAAA record exists and whether your server correctly serves the site over IPv6. If IPv6 is published but misconfigured, Let’s Encrypt may validate over IPv6 and fail.
Can redirects break AutoSSL or Certbot?
They can. A normal HTTP-to-HTTPS redirect usually works, but redirects that rewrite the ACME path or force app routes can cause 404/403. Explicitly allow /.well-known/acme-challenge/.
How do I confirm the server is using the new certificate after renewal?
Use openssl s_client against port 443 and check the notAfter date. If the date didn’t change, reload the web server and confirm it points at the right cert files.
Summary
Renewal failures get manageable once you treat them like a checklist. Confirm DNS, confirm port reachability, and confirm the challenge path is served by the right vhost.
Then run a dry-run renewal and read the log.
Fix the routing, not the symptom.
If you want a cleaner environment where these basics stay stable, run your sites on a HostMyCode VPS (or go hands-off with managed VPS hosting) and keep renewals boring—which is exactly what you want.