What the renewal error is telling you
/var/log/letsencrypt/letsencrypt.log. The line to look for names the challenge type and the URL the certificate authority (CA) tried to fetch.
TEXT
# Illustrative output format (wording varies by client and version)
Challenge failed for domain example.com
http-01 challenge for example.com
Type: connection
Detail: 203.0.113.10: Fetching http://example.com/.well-known/acme-challenge/<token>: Timeout during connect (likely firewall problem)http-01. The URL starts with http://, not https://. And the failure type is a connection problem, not a 404 or an unauthorized response. A timeout usually means packets were dropped silently, which is what most firewall deny rules do. A "connection refused" points instead to the port being reachable but nothing listening on it.
If the error is a 404 or an unauthorized response instead, the request did reach the web server, and the firewall is not your problem. That distinction alone saves a lot of time.
Why HTTP-01 always starts on port 80
/.well-known/acme-challenge/ on your web server, and the CA fetches it to prove you control the domain.
The CA always makes that first request over plain HTTP on port 80. There is no option to ask it to start on 443 or on a custom port. This is deliberate: the challenge exists to bootstrap a certificate, so it cannot assume a working one is already in place.
Let's Encrypt does follow redirects during HTTP-01, including a redirect from HTTP to HTTPS, as long as the target is on port 80 or 443. That is why a normal "redirect everything to HTTPS" rule does not break validation. But a redirect can only happen if the first connection on port 80 succeeds. Close the port and the CA never gets far enough to see your redirect.
The weeks of delay come from how renewal works. Certificates are typically renewed well before expiry, and nothing tests port 80 in the meantime. The firewall change and the failure end up far apart in time, so the firewall change rarely comes to mind.
Ruling out the usual suspects
/.well-known/ to a login page, another domain, or a non-standard port usually shows up as a 404, an unauthorized response, or an invalid redirect error. It does not show up as a timeout on the very first fetch.
A web server that is down or not listening on 80. This tends to give "connection refused" rather than a timeout. Checking the listener locally separates this from a firewall block.
If the IP is correct, there is no CAA error, and the failure is a connection timeout on an http:// URL, the firewall is the leading suspect.
Confirming the firewall is the cause
BASH
# 1. Is anything listening on port 80 locally?
sudo ss -tlnp | grep ':80 '
# 2. What does the host firewall allow? (use the one your server runs)
sudo ufw status verbose
sudo firewall-cmd --list-all
sudo iptables -S | grep -E 'dport (80|443)'
sudo nft list ruleset | grep -E 'dport (80|443)'
# On a cPanel server with CSF, check the allowed inbound TCP ports
grep '^TCP_IN' /etc/csf/csf.conf
# 3. From a different machine outside your network, not the server itself
curl -sv --max-time 10 'http://example.com/.well-known/acme-challenge/test' -o /dev/nullcurl test from outside, for example from a home connection or another server. Testing from the server itself often goes through the loopback interface or skips the rules applied to external traffic, so it can report success while the outside world gets nothing.
What you are looking for: ss shows nginx, Apache, or LiteSpeed bound to port 80, the firewall rules show 443 allowed and 80 missing or denied, and the external curl hangs until the timeout. A 404 from the external curl is actually good news. It means the port is reachable and the web server answered.
One more layer is easy to miss. Many VPS and cloud platforms run a network firewall or security group in front of the server, separate from UFW or firewalld. If the host firewall allows port 80 and the external test still times out, check that panel as well. Its rules apply before the traffic ever reaches your server.
Once port 80 is reopened, confirm with a renewal test before the real expiry date. sudo certbot renew --dry-run runs the renewal process against the Let's Encrypt staging environment without replacing your live certificate. For cPanel AutoSSL, the AutoSSL log in WHM shows whether domain control validation now passes.
Keeping HTTPS enforced without breaking renewal
NGINX
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
# Serve ACME HTTP-01 tokens directly over HTTP
location ^~ /.well-known/acme-challenge/ {
root /var/www/letsencrypt;
}
# Everything else goes to HTTPS
location / {
return 301 https://hostrequest_uri;
}
}root path must match the webroot your ACME client writes tokens to. Since Let's Encrypt follows redirects to HTTPS, a plain redirect-everything block often works too. The explicit exception just removes one more thing that can go wrong, for example a broken or misconfigured HTTPS virtual host. On Apache or LiteSpeed the same idea applies: allow /.well-known/acme-challenge/ before any rewrite rule that changes the scheme or host.
If port 80 really must stay closed, for policy or compliance reasons, HTTP-01 is the wrong challenge type. The options look like this:
- DNS-01 proves control by publishing a TXT record at
_acme-challenge, so no inbound port is needed. It requires DNS API access or a manual process, and it brings its own failure modes, covered in Adding the _acme-challenge TXT Record Doesn't Mean Every Nameserver Serves It. - TLS-ALPN-01 validates over port 443 only. Support depends on the ACME client, so check its documentation before planning around it.
- Opening port 80 only during renewal, with client hooks such as certbot's
--pre-hookand--post-hook, works but is fragile. A failed hook can leave the port open or keep it closed, and a firewall change becomes part of an automated job.
http:// URL, check the firewall before DNS or rewrite rules, and test from outside the server. If the aim of closing port 80 was to force HTTPS, reopen it with a redirect and HSTS instead. If port 80 has to stay closed, switch the certificate to DNS-01 on purpose, and run a dry-run renewal right away rather than finding out at the next expiry.