Bali, Indonesia
Insights

Closed Port 80 for Security, Now Let's Encrypt Renewal Fails

October 28, 2026
0:00
A common hardening step goes like this: the site is on HTTPS, nobody should be using plain HTTP anymore, so port 80 gets closed in the firewall. Everything keeps working for weeks. Then the certificate approaches expiry, renewal fails, and the error mentions a timeout or a firewall problem even though HTTPS is clearly reachable. The two events are connected, and the gap between them is why the link is easy to miss. This walkthrough starts from the error, eliminates the usual suspects, confirms the firewall is the cause, and then shows how to keep HTTPS enforced without breaking renewal.
With certbot, the useful detail is usually in the output of the failed run and in /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)
Three details matter here. The challenge type is 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.
ACME is the protocol Let's Encrypt and many other CAs use to issue certificates automatically. In the HTTP-01 challenge, your ACME client places a small token file under /.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.
Before touching the firewall, it is worth eliminating the causes that produce similar-looking failures. Support engineers often see these get mixed up, because "renewal failed" covers many different problems. DNS pointing somewhere else. If the domain's A or AAAA record resolves to an old server or a different IP, the CA connects to the wrong machine. Compare the IP in the error detail with your server's public IP. If they differ, the problem is DNS, not your firewall. An IPv6 address in the error deserves the same check, since the CA may try IPv6 when an AAAA record exists. CAA records. A CAA record that does not allow the CA produces a distinct CAA error, not a connection timeout. If that is what you see, Why a CAA Record Can Block Let's Encrypt Renewal and How to Check It covers it. Redirect or rewrite rules. A rule that sends /.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.
The goal is to show two things: the web server listens on port 80 locally, and port 80 is not reachable from outside. These commands are read-only.
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/null
Run the curl 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.
Closing port 80 rarely adds much protection on a site that already redirects to HTTPS. The redirect only sends a location header back. No logins or form data should be travelling over that connection. The real protection against downgrade attacks is HSTS (HTTP Strict Transport Security), a response header that tells browsers to use HTTPS for that domain without even trying plain HTTP. With a redirect plus HSTS, visitors end up on HTTPS and port 80 stays available for validation. The simplest setup keeps port 80 open, serves ACME tokens directly, and redirects everything else. A minimal nginx example using a webroot for the challenge files:
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;
    }
}
The 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-hook and --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.
Allowlisting only the CA's servers on port 80 is not a practical option. Let's Encrypt does not publish a fixed list of validation IPs and validates from several network locations, so an allowlist that works today can break without warning. On cPanel servers, the same logic applies to AutoSSL when it uses HTTP-based domain control validation. If CSF or another firewall drops port 80, validation fails in the same way. The AutoSSL log usually shows which domains failed and why. The practical takeaway: when a certificate renewal fails with a connection timeout on an 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.