Tutorial · cloudflare · Published 2026-08-16 · 3 min read
Getting the real visitor IP behind Cloudflare
Restore the real visitor IP at your origin with CF-Connecting-IP, use the web server's trusted proxy module, and prevent spoofing.
When Cloudflare proxies your traffic, your origin server sees the network address of a Cloudflare edge server, not the visitor's. Any application that reads the client IP, for logging, rate limiting, or geo decisions, is therefore wrong by default. The fix is to read the real visitor IP from the header Cloudflare adds, and to do it at the web server layer so every consumer of the address is correct at once.
Which header to read
Cloudflare sends the original visitor IP in the CF-Connecting-IP request header to your origin. It is present on every request on every plan, with no configuration, and holds a single IP address in a consistent format:
CF-Connecting-IP: 203.0.113.45
The similar-sounding True-Client-IP carries the same value but is an Enterprise-only feature intended for compatibility; on Free, Pro or Business it is not present, so use CF-Connecting-IP. Prefer it over reading addresses from X-Forwarded-For, which can contain multiple entries and be harder to parse. The earlier overview of Cloudflare's records and origin locking is the companion to this guide, because correctly trusted headers only work when you also restrict who can reach the origin.
Fix it at the server
The robust way is to teach the web server to replace the remote IP with CF-Connecting-IP only for connections from Cloudflare ranges. In nginx, use the ngx_http_realip_module with a trusted-proxy list:
set_real_ip_from 173.245.48.0/20;
set_real_ip_from 103.21.244.0/22;
real_ip_header CF-Connecting-IP;
real_ip_recursive on;
On Apache, the equivalent is mod_remoteip with a RemoteIPTrustedProxy directive listing the same Cloudflare ranges. Once configured, every log line, rate limiter and REMOTE_ADDR read sees the real client IP without any application change. Keep the IP-range list updated, because Cloudflare publishes its ranges and rotates them over time.
If you cannot touch the server config (common on shared hosting), read CF-Connecting-IP in the application language instead, but only after verifying the request really came from Cloudflare. The header arrives as $_SERVER['HTTP_CF_CONNECTING_IP'] in PHP, and you should validate it as a genuine IPv4 or IPv6 literal before use so a spoofed value cannot pass through as an arbitrary string.
Trust it safely
The single rule that makes any of this safe is: only trust the header on connections that originate from a Cloudflare IP range. If you trust CF-Connecting-IP from any source, anyone can forge the header and have the origin attribute the traffic to any IP they choose, which defeats logging and can poison rate limits or access control. That is why the module-based approach bakes the trust boundary in, rather than leaving every script to guess.
Cloudflare keeps the set of its own ranges current on its published IP list, which can be merged into your trusted-proxy config automatically. Combined with locking the origin so that only Cloudflare ranges can reach it (see the lock-down article), trusting CF-Connecting-IP behind that boundary is then correct and spoof-resistant. After making the change, verify with a test request that your log line shows the visitor IP and not a Cloudflare edge address, and confirm the site still serves through Cloudflare rather than breaking the proxy relationship, before you rely on the new addresses for analytics or rate limiting.