Table of Contents
Secure Caddy Setup Behind Cloudflare: Accurate Client IP Detection and IP-Based Access Control
Deploying Caddy behind Cloudflare’s reverse proxy requires special attention so that you can accurately detect the real visitor IP (rather than the Cloudflare edge IP), and use it for access filtering. This guide walks through the correct configuration, common pitfalls, and how to fix them.
Why Real IP Doesn’t Show by Default
- Caddy only trusts client IP headers (like
X-Forwarded-FororCF-Connecting-IP) if the incoming request comes from a trusted proxy. Otherwise, it uses the proxy IP as bothremote_ipandclient_ip. - By default, Cloudflare allows clients to send custom
X-Forwarded-Forheaders—which can be spoofed—unless Cloudflare rewrites them - If
trusted_proxies_strictis not enabled, Caddy picks the first untrusted IP in a left‑to‑right parsing of the header—this can be incorrect if proxies append IPs.
Core Global Options for Caddyfile
{
debug
servers {
trusted_proxies static <all Cloudflare IP ranges here>
trusted_proxies_strict
client_ip_headers Cf-Connecting-Ip X-Forwarded-For
}
}
Why each part matters:
- debug enables verbose logs to troubleshoot IP matching.
- trusted_proxies static lists Cloudflare IP CIDRs only—Caddy will only trust headers from these ranges.
- trusted_proxies_strict forces right‑to‑left header parsing—secure behavior when proxies append multiple IPs.
- client_ip_headers prioritizes
CF-Connecting-IP, with fallback toX-Forwarded-For.
Enabling IP-Based Access Control
example.com {
@allowed client_ip 203.0.113.0/24 198.51.100.45
@blocked not client_ip 203.0.113.0/24 198.51.100.45
respond @blocked "Forbidden" 403
reverse_proxy localhost:8123
}
Here, any request whose parsed client_ip matches your approved IPs gets proxied; all others are blocked early with a 403 response.
Common Errors & How to Fix Them
- Error: “server block without any key is global configuration…” – Fix: The global `{ … }` block must be at the very top of your Caddyfile.
- Error: Real visitor IPs still not showing – Fix: Ensure you fetched all Cloudflare ranges into
trusted_proxies—missing one invalidates header trust. - Unexpected 403 for all clients – Fix: Check logs under `DEBUG` level to verify `client_ip` vs `remote_ip` values; often headers are ignored due to untrusted proxy.
Example Debug Log Output
{"remote_ip":"172.x.x.x","client_ip":"203.x.x.x","request":{…},"status":403, …}
If client_ip still shows the proxy, Caddy is not treating that proxy as trusted. Add it to your list.
Optional—Automate Cloudflare IP Updates
Maintaining Cloudflare IP lists manually can be error-prone. You can automate using the caddy-cloudflare-ip plugin, which fetches and refreshes ranges periodically within Caddy.
Checklist
- Place global block at top with
trusted_proxies,strict, and header config. - Insert site block with a
client_ip-based allowlist and blocked response. - Enable
debugand tail logs to ensure correct IP detection. - Configure Cloudflare to rewrite incoming headers—prevent spoofing.
- (Optional) Automate IP list updates via plugin or script.
Why This Setup Works
This approach guarantees:
- Only Cloudflare edge servers can supply trusted IP headers.
- If Cloudflare is configured to overwrite client headers, no spoofing is possible.
trusted_proxies_strictcorrectly identifies the true visitor IP amid chains of proxies.- Your own client IP ranges are used only to decide access—not to define trust boundaries.
It protects your origin server from unauthorized access while reliably identifying end users’ real IP addresses behind Cloudflare.
Appreciate your comments.