The visitor’s address is in the CF-Connecting-IP request header. Configure your web server to read that instead of the connection’s source address — and to believe it only on connections from Cloudflare’s published IP ranges.
Nothing is broken. Once a DNS record is proxied, Cloudflare is a reverse proxy in front of your server, so Cloudflare is the client as far as your origin is concerned. The TCP connection genuinely originates at a Cloudflare data centre, and REMOTE_ADDR — along with everything that derives from it — genuinely describes that connection. The visitor has not disappeared; they have moved from the connection into a header.
The headers that carry the address
Cloudflare adds several, and they are not interchangeable. The differences matter mostly when you get to parsing:
| Header | Contains | Availability | Watch out for |
|---|---|---|---|
CF-Connecting-IP | Exactly one address: the client that connected to Cloudflare | All plans, added automatically | Only ever sent from Cloudflare’s edge to your origin |
CF-Connecting-IPv6 | The original IPv6 address | With Pseudo IPv4 in Overwrite Headers mode | Only relevant if you use Pseudo IPv4 at all |
True-Client-IP | The same single address, under a different name | Enterprise, and only once the Managed Transform is enabled | Not added by default; a stacked CDN can forge it |
X-Forwarded-For | A comma-separated chain, visitor first | All plans | Grows by one entry per proxy ahead of Cloudflare |
Cloudflare documents True-Client-IP as differing from CF-Connecting-IP in name alone. It exists for Enterprise customers whose load balancers or firewalls already read that header name and cannot be reconfigured.
Why X-Forwarded-For is the tempting wrong answer
If nothing sits in front of Cloudflare, X-Forwarded-For has the same value as CF-Connecting-IP, which is exactly why so many setups appear to work and then quietly stop. The moment a request passes through another proxy first, Cloudflare appends rather than replaces. Cloudflare’s own worked example: a visitor at 203.0.113.1 going through proxy A (198.51.100.101) and proxy B (198.51.100.102) arrives at your origin as
x-forwarded-for: 203.0.113.1,198.51.100.101,198.51.100.102Read the last entry and you ban proxy B. Read the first and you trust whatever the earliest hop claimed. Cloudflare’s recommendation is unambiguous — use CF-Connecting-IP or True-Client-IP for logging and applications, because both hold a single address in a consistent format. Treat XFF as a diagnostic, not as an identity.
Configuring your web server
The pattern for restoring the real IP is the same everywhere: name the header, then name the addresses you will accept it from. Both halves are required.
Nginx
Use the built-in ngx_http_realip_module. One set_real_ip_from line per Cloudflare range, then the header:
set_real_ip_from 198.51.100.0/24; # repeat for every published range
real_ip_header CF-Connecting-IP;To keep the address in the access log, Cloudflare notes that you add $http_cf_connecting_ip — and, if you want it, $http_x_forwarded_for — to your log_format directive. It is the same header throughout, spelled three ways depending on where you are looking: CF-Connecting-IP in Cloudflare’s reference, cf-connecting-ip on the wire, and $http_cf_connecting_ip once Nginx has turned it into a variable.
Apache
Cloudflare stopped updating and supporting mod_cloudflareas of Debian 9 and Ubuntu 18.04 LTS, and now points to Apache’s own mod_remoteip. Enable it with a2enmod remoteip, then:
RemoteIPHeader CF-Connecting-IP
RemoteIPTrustedProxy 198.51.100.0/24 # repeat for every published rangeThe step people miss is the log format itself. %hlogs the connection’s address and will keep printing Cloudflare’s; swap it for %a in your combined LogFormat line so the restored address is what lands in the file.
Cloudflare’s restoring original visitor IPs page carries the equivalent recipes for Lighttpd, LiteSpeed, IIS and Tomcat, plus a one-line PHP fallback that reassigns REMOTE_ADDR for scripts without touching the server logs.
The trust boundary, which is the whole point
A header is a string that someone put in a request. Through Cloudflare it is reliable; sent straight to your server it is whatever the sender typed. If your origin address is discoverable — and Cloudflare notes it often is, through historical DNS records or mail configuration — then an application that reads CF-Connecting-IP unconditionally has handed its rate limiter, its ban list and its geolocation to anyone willing to set one header.
Two measures, and the second is the one that actually holds:
- Give the module a trusted proxy list. That is what
set_real_ip_fromandRemoteIPTrustedProxyare for. Cloudflare publishes the ranges at cloudflare.com/ips and says plainly that the list needs updating regularly — a config written three years ago is probably incomplete now, so pull it from the published source on a schedule rather than pasting it once. - Refuse the connections in the first place. Cloudflare recommends blocking all traffic to ports 80 and 443 that does not come from its ranges or from partners you trust. Then a forged header never reaches the parser, and the same allowlist doubles as the fix for the firewall-shaped causes of error 522.
When the header is not there at all
Four documented reasons, in the order worth checking:
- The record is grey-clouded. Cloudflare only sends the header on traffic from its edge to your origin. On a DNS-only record there is no edge on the path, and the connection address is already the visitor. See what the orange cloud means in Cloudflare if you are not sure which records are proxied.
- A Managed Transform is stripping it. Remove visitor IP headers removes
cf-connecting-ip,true-client-ipand the visitor entry fromx-forwarded-fortogether. It is a privacy feature, and it is mutually exclusive with the Enterprise transform that addsTrue-Client-IP. - Pseudo IPv4 is set to Overwrite Headers. The header is present but holds a Class E address such as
240.16.0.1, hashed from the visitor’s IPv6 address. The real one is inCF-Connecting-IPv6. - A Worker is in the path. In same-zone subrequests the value reflects
x-real-ip, which a Worker script can alter; in cross-zone subrequests Cloudflare deliberately sets it to2a06:98c0:3600::103instead of the client address. If your traffic passes through a Worker, verify the value rather than assuming it.
One last trap worth knowing about, because it produces a header that vanishes for no visible reason: Cloudflare may drop request headers whose namesit considers invalid by Nginx’s rules — a custom header with a dot in the name, for instance. If you are forwarding a visitor address under a name of your own devising, that is a good place to look before you suspect the proxy.
FAQ
How do I get the real visitor IP behind Cloudflare?
Read the CF-Connecting-IP request header instead of the address of the connection itself. Every proxied request Cloudflare forwards to your origin carries it, and it always holds exactly one address.
What is the CF-Connecting-IP header?
It is the header Cloudflare adds to a proxied request to give the origin the client IP address that connected to Cloudflare. Cloudflare only sends it on traffic from its own edge to your origin, so it never appears on a request that reached your server some other way.
Should I use CF-Connecting-IP or X-Forwarded-For?
Cloudflare recommends CF-Connecting-IP or True-Client-IP over X-Forwarded-For, because both contain a single address in a consistent format. X-Forwarded-For is a list that grows by one entry for every proxy in front of Cloudflare, so parsing it correctly is harder than it looks.
Can the CF-Connecting-IP header be spoofed?
Not through Cloudflare, but a request that reaches your origin directly can carry any header its sender likes. That is why real-IP modules take a trusted proxy list, and why Cloudflare recommends blocking traffic at the origin that does not come from its published IP ranges.
Why is CF-Connecting-IP missing from my requests?
The three documented reasons are that the record is not proxied, that the Remove visitor IP headers Managed Transform is enabled on the zone, or that Pseudo IPv4 is set to Overwrite Headers, which replaces the value with a Class E address and moves the real one into CF-Connecting-IPv6.