Client IP resolver (X-Forwarded-For and trusted proxies)
Paste an X-Forwarded-For chain, a socket peer address and your trusted proxy CIDRs, and see exactly which address your stack should treat as the client.
Resolve the real client IP
What this tool does#
It runs the only correct algorithm for deriving a client IP from a forwarded header chain: start at the socket peer address, walk leftwards through X-Forwarded-For, discard every address you trust, and stop at the first address you do not. Whatever you stop on is the client. Everything to the left of it is attacker controlled and must be ignored.
The tool shows the walk step by step, so you can see which entry was accepted and why. It also computes the answer a fixed hop count would give, because Envoy, some ingress controllers and several CDNs count hops from the right instead of matching CIDRs, and the two models silently disagree the moment your topology changes.
Why the naive reads are wrong#
There are two shortcuts that appear in a great deal of production code, and both are broken.
| Shortcut | What it returns | Why it fails |
|---|---|---|
xff.split(',')[0] | The leftmost entry | The client writes the leftmost entry. curl -H "X-Forwarded-For: 127.0.0.1" sets it to anything. |
xff.split(',').pop() | The rightmost entry | That is your own load balancer, not the client. Correct only when exactly one trusted proxy exists and it overwrites rather than appends. |
| Trusting the header at all with no trust list | Anything | If the header is honoured on requests that did not arrive through your proxy, every rate limit and IP allowlist keyed on it is bypassable. |
The rule that survives topology changes: trust is a property of the hop, not of the position. Configure which addresses are your proxies, then let the position fall out of that.
Reading the result#
- Resolved client IP is the address to log, rate limit on, and geolocate.
- If the walk stops at the socket peer, no forwarded entry was usable. Either nothing in front of you is appending the header, or the thing in front of you is not in your trusted list.
- If the walk consumes the entire header and lands on the leftmost entry, every hop was trusted. That result is only as good as your outermost proxy's discipline about overwriting inbound
X-Forwarded-For, which is covered in client IP spoofing through proxies. - If the fixed hop count answer differs from the CIDR answer, one of the two configurations is wrong. That mismatch is the single most common cause of a client IP that breaks the day a CDN is added or removed.
Turning the answer into configuration#
Once you know which addresses are trusted, the configuration is mechanical. Configuring trusted proxies has the exact directives for nginx, HAProxy, Envoy, Caddy, Traefik, Express, Django, Rails and Spring Boot. If your proxy layer is L4 and cannot add a header at all, the answer is not a header: it is the PROXY protocol.
Frequently asked questions#
Should I take the first or the last IP in X-Forwarded-For?#
Neither, unconditionally. Walk from the right, skipping addresses that are in your trusted proxy list, and take the first address that is not. The leftmost entry is whatever the client claimed and the rightmost is your own infrastructure.
What if there are several X-Forwarded-For headers instead of one?#
Most servers join repeated field lines with a comma and a space before your code sees them, producing a single logical list, which is what RFC 9110 requires for list-valued fields. Some do not. Because a client can send its own X-Forwarded-For and your proxy appends to it, the joined list is exactly the case this tool models, and the trust walk handles it correctly.
Does the tool send my header anywhere?#
No. Everything runs in your browser. Nothing is uploaded, logged or stored.
How do I choose between a CIDR list and a hop count?#
Use a CIDR list when you control the proxies and their addresses are stable. Use a hop count when the addresses in front of you are large and changing, such as a CDN edge fleet, and the number of hops is fixed by contract. Hop counting fails silently when someone adds a hop, so pin it in configuration review.
Why does my resolved IP look like an IPv4 address written inside IPv6?#
An ::ffff:203.0.113.7 form is an IPv4-mapped IPv6 address, produced when a dual stack socket accepts an IPv4 connection. The tool matches those against IPv4 CIDRs. Your application code and your trusted proxy lists often do not, which is a common reason a trust check passes in staging and fails in production.
Primary sources#
Every normative claim on this page is checked against the specification or the vendor documentation listed here. Where behaviour is version dependent, the version is named in the text.