Why Do My Server Logs Show Cloudflare’s IP, Not the Visitor’s?

Every entry in the access log is a Cloudflare address, the rate limiter is throttling continents at a time, and the ban list has stopped meaning anything. Here is where the visitor went, how to get them back, and the reaction that turns this into an outage.

Updated September 12, 2026 · 9 min read

Because Cloudflare is the client now. The visitor’s address moved into the CF-Connecting-IP request header. Configure your web server to log that header instead of the connection — 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.

Where the visitor IP travels, and the one condition that makes it trustworthyONE PROXIED REQUEST, AT YOUR ORIGIN172.71.0.1TCP peer — a Cloudflare address203.0.113.7cf-connecting-ip — the visitorDid this connection arrive froma published Cloudflare IP range?YesThe header is the visitor. Log it,rate limit on it, geolocate it.NoWhoever opened the connectionchose that value. Discard it.
A request header is only as trustworthy as the connection that carried it, which is why every real-IP module asks you for a list of trusted proxies.

What breaks when every visitor shares an address

Cloudflare’s IP addresses are shared by every proxied hostname, so this is not only a cosmetic logging problem. Anything that counts, identifies or punishes by address quietly changed meaning the moment the record went orange, and none of it throws an error:

  • Analytics that count unique visitors now count data centres. Server-side stats collapse; a JavaScript analytics tag is unaffected, because it reads the address from the visitor’s own connection.
  • Per-IP rate limits and one-vote-per-IP logic apply to everyone behind the same edge address at once. The limit still fires — just against a crowd instead of a person.
  • Ban lists and blocklists either hit nobody or hit a whole region, and the country rules built on REMOTE_ADDR now read Cloudflare’s location rather than the visitor’s. Geolocation has its own header, CF-IPCountry, but Cloudflare only adds it once the Add visitor location headers Managed Transform is enabled — it is not there by default.

The expensive one is the failure that fixes itself into an outage. Cloudflare warns that to your origin’s firewall, proxied traffic looks like a small number of sources sending a high volume of requests — exactly the shape that automatic blocking and rate limiting are built to stop. So fail2ban, or a host firewall left on its defaults, does what it was told: it bans the noisiest addresses. Those addresses are Cloudflare, every visitor is behind them, and the site goes dark for everyone at once. What you see next is error 522, because the proxy can no longer reach the origin either.

That is the real reason to restore the visitor IP before you tune anything else: until your server reads the right address, every security tool you own is aiming at the wrong target — and some of them are aiming at your own traffic.

The headers that carry the address

Cloudflare adds several, and they are not interchangeable. The differences matter mostly when you get to parsing:

HeaderContainsAvailabilityWatch out for
CF-Connecting-IPExactly one address: the client that connected to CloudflareAll plans, added automaticallyOnly ever sent from Cloudflare’s edge to your origin
CF-Connecting-IPv6The original IPv6 addressWith Pseudo IPv4 in Overwrite Headers modeOnly relevant if you use Pseudo IPv4 at all
True-Client-IPThe same single address, under a different nameEnterprise, and only once the Managed Transform is enabledNot added by default; a stacked CDN can forge it
X-Forwarded-ForA comma-separated chain, visitor firstAll plansGrows 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.102

Read 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 one header throughout, and the spelling changes with the context rather than the meaning: CF-Connecting-IP in Cloudflare’s reference, cf-connecting-ip on the wire, $http_cf_connecting_ip once Nginx has turned it into a variable, and HTTP_CF_CONNECTING_IP inside PHP’s $_SERVER. Header names are case-insensitive, so the CF Connecting IP you see written without hyphens in forum posts is the same thing again.

Apache

Cloudflare stopped updating and supporting mod_cloudflare as 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 range

The step people miss is the log format itself. %h logs 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:

  1. Give the module a trusted proxy list. That is what set_real_ip_from and RemoteIPTrustedProxy are for. Cloudflare publishes the ranges at cloudflare.com/ips. The ranges do not change often, which is the trap: a list pasted in by hand survives long enough to feel permanent, and Cloudflare publishes new ranges to that page before putting them into production. Pull it from the published source — or the API — rather than from memory.
  2. 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-ip and the visitor entry from x-forwarded-for together. It is a privacy feature, and it is mutually exclusive with the Enterprise transform that adds True-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 in CF-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 to 2a06:98c0:3600::103 instead of the client address. If your traffic passes through a Worker, verify the value rather than assuming it.

FAQ

Why do my server logs show Cloudflare IP addresses instead of the visitor's?

Because on a proxied hostname Cloudflare is the client that connects to your origin, so the connection really does come from a Cloudflare address. Cloudflare documents that the original visitor address is moved into an appended request header called CF-Connecting-IP, and your web server has to be configured to log that header instead of the connection.

Why do all my visitors appear to have the same IP address?

They are sharing the handful of Cloudflare addresses that served them, because Cloudflare IP addresses are shared by every proxied hostname. Anything that counts or identifies people by address — unique visitor stats, rate limits, ban lists, one-vote-per-IP logic — is really counting Cloudflare data centres until you restore the real address.

How do I restore the original visitor IP in Nginx or Apache?

In Nginx, use the built-in real IP module with real_ip_header CF-Connecting-IP plus one set_real_ip_from line for each Cloudflare range. In Apache, Cloudflare now recommends mod_remoteip with RemoteIPHeader CF-Connecting-IP and RemoteIPTrustedProxy entries, and you must also change %h to %a in your LogFormat or the log keeps printing Cloudflare's address.

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 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 preserves the real one in CF-Connecting-IPv6.

Manage this from your phone

Orange Cloud is a native iOS and Android client for Cloudflare. Sign in with Cloudflare OAuth and flip proxy status, edit DNS records, and read traffic analytics from anywhere.

Get Orange Cloud