Locking Your Origin to Cloudflare Breaks Let's Encrypt — Why the /.well-known/ Carve-Out Can't Work
By Josh Comstock · July 2026 · ~6 min read
Lock your origin's web ports to Cloudflare's IP ranges and direct-to-origin attacks stop at the socket. It's the structural fix we recommend in the origin-bypass write-up — and it hides a time bomb. Weeks later, certificates quietly stop renewing: Let's Encrypt HTTP-01 challenges time out, cPanel AutoSSL reports DCV failures on every grey-cloud hostname, and nothing in your web logs explains why — because the validators' connections never got far enough to be logged.
The intuitive fix — a firewall carve-out that lets /.well-known/acme-challenge/ requests through — cannot work, ever. Not "is fiddly to configure": it is structurally impossible for a packet-content match to admit a new TCP connection. We learned this the production way. Here's the mechanics, and the two postures that actually hold up.
The Symptom
Your origin firewall accepts Cloudflare's ranges on ports 80 and 443 and drops everything else. Proxied (orange-cloud) traffic is unaffected. But certificate validation traffic isn't proxied:
- Let's Encrypt HTTP-01 validators fetch
http://yourdomain/.well-known/acme-challenge/<token>— from Let's Encrypt's own infrastructure, straight at whatever the hostname resolves to. For a grey-cloud (DNS-only) hostname, that's your origin, on port80, from a non-Cloudflare IP. The lock drops the connection at the SYN. - cPanel/Sectigo AutoSSL DCV works the same way — an HTTP fetch of a validation file, from validator IPs that aren't Cloudflare's. Every grey-cloud service subdomain (
cpanel.,webmail.,mail.) fails DCV on the next AutoSSL run.
The failure is silent by design: dropped SYNs never become HTTP requests, so nothing appears in the access log. The first visible symptom is often an expiry warning email — or a browser certificate error on a customer's webmail.
Why the /.well-known/ Carve-Out Cannot Work
The obvious answer is a rule like this, placed above the DROP:
# THIS DOES NOT WORK — shown so you don't build it
iptables -A INPUT -p tcp --dport 80 -m string --string "/.well-known/" \
--algo bm -j ACCEPT
It reads perfectly: accept any packet containing the challenge path, drop the rest. And its packet counter will sit at zero forever while renewals fail around it.
The reason is TCP itself. Before an HTTP request can exist, the client and server complete a three-way handshake — SYN, SYN-ACK, ACK. Those packets carry no application payload. The string /.well-known/ the rule is waiting for arrives only later, inside the HTTP request — but that request is only ever sent on an established connection. A payload match can therefore never admit a new connection: the validator's SYN contains nothing matchable, falls through the string rule, and hits the DROP. The handshake never completes, so the packet that would have matched is never sent. The carve-out isn't leaky — it's a logical impossibility.
We shipped exactly this rule in Swatter's origin lock and it survived until real-world attribution caught it: the accept rule's live counter stayed at zero while Let's Encrypt validators were SYN-dropped on :80 and every grey-cloud hostname's HTTP-01 and AutoSSL DCV failed. The toggle is retired as of v2.5.0.
You Can't Allowlist the Validators Either
The next instinct — accept Let's Encrypt's source IPs — is closed off deliberately. Let's Encrypt validates each challenge from multiple network perspectives and doesn't publish the addresses, precisely so infrastructure can't special-case the validator while serving something else to everyone. Sectigo's DCV agents are equally unpublishable. Any allowlist you scrape together today breaks without notice.
The Two Postures That Actually Work
1. Lock 443 Only (Recommended)
Leave port 80 unlocked and let it serve exactly two things: HTTPS redirects and /.well-known/ challenges. Your content lives on 443, which stays locked to Cloudflare's ranges — so a direct-to-origin attacker gets, at most, a 301 pointing back to the front door the lock defends. Renewals work because validators reach :80 like anyone else.
# Lock only the content port
iptables -A INPUT -p tcp --dport 443 -m set --match-set cf_edge src -j ACCEPT
iptables -A INPUT -p tcp --dport 443 -j DROP
The trade is honest: :80 remains reachable, but on a server that redirects everything to HTTPS it exposes redirects and challenge files, not content. That's a far smaller surface than broken renewals across every site on the box.
2. DNS-01 Validation, Then Lock Both Ports
If you genuinely need :80 dropped, move validation off HTTP entirely. ACME DNS-01 proves control with a TXT record, so no validator ever needs to reach your origin — then locking 80 and 443 is sound. The cost is DNS automation: your ACME client needs API access to your DNS (with Cloudflare-hosted zones, certbot-dns-cloudflare or acme.sh handles it), and cPanel's AutoSSL must be on DNS-based DCV. If any site on the box still depends on HTTP validation, you're back to posture 1.
How Swatter Ships It Now
Swatter's optional origin lock defaults to ORIGIN_LOCK_PORTS="443" as of v2.5.0. apply warns on any port list that includes 80, the retired ACME toggle is ignored, and teardown removes the legacy /.well-known/ rule from boxes an older version configured. The rest of the lock's safety spine is unchanged: rules build accept-first drop-last so a mid-build failure fails open, an empty Cloudflare range list installs no restriction at all, and drop mode won't install without a preflight that classifies what it would have dropped.
Related: How attackers bypass Cloudflare and hit your origin directly · How to block attackers on a cPanel server behind Cloudflare.