ccnames.dev

cnames.dev / blog

Engineering11 min read

Why we give you the real client IP (and how)

Once you proxy custom domains, your origin sees the proxy IP. Here is how to forward and trust the real client IP with X-Forwarded-For and PROXY protocol — without getting spoofed.

S
Saeed
July 3, 2026

The moment you put a proxy in front of customer domains, your origin stops seeing the visitor's IP address and starts seeing yours. For anything that does rate limiting, fraud scoring, geo rules, or audit logging, that is a correctness bug that silently corrupts data. Here is why it happens, and how to forward the real client IP without letting anyone spoof it.

Visitor (203.0.113.7)
   │  TCP connection A
   ▼
Proxy / edge (198.51.100.2)
   │  TCP connection B  <-- origin's socket peer is 198.51.100.2, not the visitor
   ▼
Origin  ->  REMOTE_ADDR = 198.51.100.2  (wrong!)

Why the origin sees the wrong IP

A proxy does not forward packets; it terminates one TCP connection and opens a new one to the origin. So the origin's kernel-level peer address is the proxy. Unless the visitor's address is carried at a higher layer, it is simply gone by the time your app reads REMOTE_ADDR.

Option 1 — X-Forwarded-For

The proxy adds the visitor's IP to an HTTP header. Each hop appends, so the header is a list, left to right, oldest to newest:

X-Forwarded-For: 203.0.113.7, 198.51.100.2

The leftmost entry is the original client — but only if you trust the chain. This is universal for HTTP/HTTPS and what most web frameworks consume.

Option 2 — PROXY protocol

PROXY protocol prepends the real connection info at the TCP layer, before any HTTP is parsed. Use it for non-HTTP traffic, or when you want the origin to know the client IP without relying on HTTP headers. It requires both proxy and origin to speak it. For a typical web app, a correctly-trusted X-Forwarded-For is simpler and enough.

The spoofing trap (this is the important part)

X-Forwarded-For is just a header. Any client can send one:

curl https://app.acme.com -H "X-Forwarded-For: 1.2.3.4"

If your app trusts that unconditionally, an attacker sets their IP to whatever they want — defeating rate limits, IP allow-lists, and geo restrictions. The rule: only trust forwarded headers when the connection originates from your own edge ranges, and strip/replace any client-supplied value at the trust boundary. Trust a specific number of hops or specific source IPs, never “always.”

Framework configuration

Every framework has an opt-in for exactly this — configure it, do not hand-roll it:

How cnames.dev handles it

Our edge forwards the client address on every proxied request and signs its own hop, so your origin can trust it by verifying the request came from our ranges. Your app keeps its normal trust-proxy configuration and reads the real visitor IP — no corrupted analytics, no spoofable rate limits. This is stage six of the proxy anatomy; the full design is in the complete guide.

Building a SaaS that needs custom domains? cnames.dev gives every customer their own domain — SSL, edge routing, and per-tenant isolation in one API call. Free for 25 domains, no demo call. Start free · Read the docs

Frequently asked questions

Why does my origin see the proxy IP instead of the visitor?

Because a proxy opens a new TCP connection to your origin. The origin's socket peer is the proxy, so REMOTE_ADDR / req.socket.remoteAddress is the proxy IP. The visitor's address must be carried in an application header (X-Forwarded-For) or via PROXY protocol.

Is X-Forwarded-For safe to trust?

Only from proxies you control. Any client can send a fake X-Forwarded-For, so if your app trusts it unconditionally, attackers can spoof their IP to bypass rate limits or geo rules. Trust it only when the connection comes from your known edge ranges, and read the correct entry.

X-Forwarded-For vs PROXY protocol — which should I use?

X-Forwarded-For is an HTTP header, simple and universal for HTTP/HTTPS. PROXY protocol operates at the TCP layer and preserves the client IP for non-HTTP traffic or when you want the origin to see it before parsing HTTP. For most web apps, a correctly-trusted X-Forwarded-For is enough.

Keep reading

S

SaeedFounder, cnames.dev — runs custom-domain infra for 5,000+ production sites at Lindo.ai.