Caddy often reports its own IP address instead of the actual client IP when proxied behind Cloudflare.
This happens because Cloudflare’s edge servers terminate the client’s TLS connection and establish a new connection to your origin server (Caddy). By default, Caddy only sees the IP of the Cloudflare edge server, not the original client. Cloudflare, however, sends the original client’s IP in a special HTTP header, typically CF-Connecting-IP.
Here’s how to configure Caddy to trust and use that header:
1. Enable the forwarded directive
The forwarded directive tells Caddy to trust specific headers for client IP information. You need to explicitly tell Caddy which headers to trust and in what order.
forwarded {
# Trust the CF-Connecting-IP header from Cloudflare
# This header contains the original client's IP address.
realip cloudflare
}
Add this block to your Caddyfile within the http or https block, or at the global level if you want it to apply to all sites.
2. Configure realip for Cloudflare
The realip cloudflare option is a pre-defined configuration that knows to look for CF-Connecting-IP. It also handles other Cloudflare-specific headers and trusts Cloudflare’s IP ranges.
3. Ensure Cloudflare is sending the header
By default, Cloudflare does send the CF-Connecting-IP header. However, if you’ve made custom modifications to Cloudflare’s settings (e.g., Page Rules, Transform Rules, or Workers), you might have inadvertently removed or altered this header.
Diagnosis:
Use curl -v from outside your network (or from a different IP address) to check the headers Caddy is receiving.
curl -v https://your-domain.com
Look for CF-Connecting-IP in the request headers printed by curl. If it’s missing, the problem is likely on the Cloudflare side.
Fix:
If the CF-Connecting-IP header is missing from Cloudflare, you might need to re-enable it. In Cloudflare’s dashboard:
- Go to SSL/TLS > Edge Certificates.
- Ensure Always Use HTTPS is enabled.
- Ensure Automatic HTTPS Rewrites is enabled.
- Check your Page Rules or Transform Rules to ensure no rule is deleting the
CF-Connecting-IPheader. A common mistake is a rule that "Remove CF-Connecting-IP" or "Remove all headers" without explicitly keeping this one.
4. Trust Cloudflare’s IP Ranges
For added security, Caddy’s forwarded directive can also be configured to only trust IP addresses from specific CIDR blocks. Cloudflare publishes a list of their IP ranges. While realip cloudflare often handles this implicitly, it’s good practice to be aware of it.
Diagnosis:
If realip cloudflare isn’t working, or if you want to be more explicit, you can list Cloudflare’s IPs. You can fetch them dynamically:
curl https://www.cloudflare.com/ips-v4
curl https://www.cloudflare.com/ips-v6
Fix:
Manually add these CIDR blocks to your forwarded directive:
forwarded {
# Explicitly list Cloudflare IPs
trusted_proxies 173.245.48.0/20 103.21.244.0/22 103.162.212.0/22 103.172.242.0/22 141.101.64.0/18 108.162.192.0/18 192.0.78.0/23 198.41.192.0/18 172.64.0.0/13 131.0.72.0/22 2400:cb00::/32 2606:4700::/32 2804:f800::/32 2405:8100::/32 2a05:70c0::/32 2606:4700:3030::/48 2606:4700:3031::/48
realip CF-Connecting-IP
}
Note: The list of IPs above is a snapshot and can change. It’s best to dynamically fetch and manage this list if possible, or use realip cloudflare which is maintained by Caddy.
The trusted_proxies directive ensures that only requests originating from these known Cloudflare IP addresses are considered valid for extracting the client IP from the CF-Connecting-IP header. Any request coming from an IP not in this list will be ignored for the purpose of IP spoofing.
5. Caddy v2.5.0 and Later Simplification
If you are running Caddy v2.5.0 or later, the forwarded directive is enabled by default and intelligently tries to detect trusted proxies. For Cloudflare, it often works out of the box without explicit configuration if Caddy can resolve Cloudflare’s IP ranges.
Diagnosis:
Check your Caddy version: caddy version. If it’s 2.5.0 or newer, try without any forwarded directive first.
Fix:
If it doesn’t work on v2.5.0+, explicitly add the forwarded block as shown in step 1. The realip cloudflare option is the most robust and recommended approach.
6. Logging and Verification
After applying the configuration, verify that Caddy is logging the correct client IP.
Diagnosis:
Add the client IP to your log directive in the Caddyfile:
log {
output stdout
format json
# Add the client IP to the log entry
try_values "{remote_ip}"
}
Then, access your site and check the logs. You should see the actual client IP address instead of a Cloudflare IP.
Fix:
Restart Caddy after modifying the Caddyfile.
systemctl restart caddy
You should now see the real client IP in your logs.
The next error you’ll encounter is likely related to your application logic not correctly handling the X-Forwarded-For header if you have multiple layers of proxies or if Caddy itself is behind another proxy.