You write an allowlist. It matches nobody. You widen it, and it still matches nobody, because behind a CDN the connecting address is always the CDN. Then you switch to reading the forwarded header instead, it works immediately, and you have quietly created an authentication bypass that anyone can use by sending one header.
With a CDN in front, the TCP connection reaching your origin comes from the edge. Your visitor's address is not in the packet at all. It is in a header the CDN added:
CF-Connecting-IP, the visitor's address, Cloudflare-specific.X-Forwarded-For, a comma-separated chain that any hop may append to.X-Real-IP, whatever the last proxy decided it meant.So client_ip 203.0.113.5 can never match a real user, and every request looks like it came
from a handful of edge addresses.
192.168.x address, so a
client_ip 192.168.0.0/16 matcher rejects your laptop from ten feet away. The load-bearing entry
is your public egress IP. Keep the private ranges only for the direct-to-origin path, hitting the box by LAN
IP with a Host header.
Reading a forwarded header is correct, and only if you have told the server whose word to take for it. In Caddy that is a global block naming the CDN's ranges:
{
servers {
trusted_proxies static 173.245.48.0/20 103.21.244.0/22 ...
}
}
Now client_ip resolves to the real visitor for connections arriving from those ranges, and
your matcher works. nginx spells it set_real_ip_from plus real_ip_header, and
Traefik has a forwardedHeaders.trustedIPs list. Same idea everywhere.
Pull the ranges from the provider rather than pasting a list from a blog post, because they change:
curl -s https://www.cloudflare.com/ips-v4
curl -s https://www.cloudflare.com/ips-v6
This is the part worth being blunt about. If your config reads CF-Connecting-IP without
declaring who is allowed to set it, then anyone who can reach your origin directly can claim to be
any address they like:
# against an origin that trusts the header unconditionally
curl -H "CF-Connecting-IP: 192.168.1.50" https://origin-ip/admin
curl -H "X-Forwarded-For: 203.0.113.5" https://origin-ip/admin
Your allowlist is now a suggestion. Declaring trusted proxies fixes this in the same stroke as making the matcher work: a request that did not arrive from a trusted range has its forwarded headers ignored, so a forged header falls through to whatever your closed branch is.
Verify it from outside, with both a forged private address and a forged copy of your real egress IP. Both must be refused.
The bypass above needs the attacker to reach your origin directly, which people assume is hard. It often is not. Historical DNS records, certificate transparency logs, an unproxied subdomain, or a mail server on the same address will all give it away.
So treat the CDN as a performance and filtering layer, not as an access control. The origin should refuse anything that did not come through the CDN:
# only accept connections from the CDN's ranges at the firewall
for r in $(curl -s https://www.cloudflare.com/ips-v4); do
ufw allow from "$r" to any port 443 proto tcp
done
ufw deny 443/tcp
Add your own management path before you enable that, and see restricting access by source IP for how that goes wrong.
X-Forwarded-For is a list, not an address. Each proxy appends. Naively
taking the first entry takes whatever the client sent, which is attacker-controlled. Take the entry your
trusted proxy added, which is what a proper trusted_proxies implementation does for you.Once an allowlist depends on your home address, it joins the pile of things that break the day your ISP renumbers you, with symptoms that look unrelated to each other. Reconcile it from the live value rather than editing it by hand. See one source of truth for a dynamic home IP.