Log the IP address that your own trusted proxy observed, which is usually the rightmost entry it appended to X-Forwarded-For, not the leftmost value. The leftmost entry is supplied by the client and can be forged. Record the full header too, labelled as unverified, so investigators can see what was claimed.
Why the obvious choice is the wrong one
X-Forwarded-For is a comma separated list. Each proxy appends the address it saw, so the leftmost entry looks like the original client. MDN notes that the header is a de facto standard rather than part of a current specification, and that any part not added by a trusted proxy may be spoofed or malformed.
That matters for logs because the client IP is the field you will filter on during an investigation. If an attacker sends their own X-Forwarded-For header and you log the leftmost value, your logs name an address of the attacker's choosing. Rate limits and blocklists built on that field fail the same way.
MDN goes further: if the server can be reached directly from the internet, even though a proxy normally sits in front of it, no part of the list is safe to use for security decisions. Closing the direct path is part of the fix.
Three ways to pick the address
MDN describes three approaches. They differ in how much they trust.
| Approach | How it works | Use it for |
|---|---|---|
| Leftmost valid public IP | Take the first entry that is a valid, non private address | Analytics, coarse geography. Not security |
| Trusted proxy count | Configure N proxies, then take the entry at offset N minus 1 from the right | Fixed topologies, such as one load balancer |
| Trusted proxy list | Scan from the right, skipping addresses on your proxy list, and take the first that is not listed | Changing topologies, CDNs, multiple tiers |
With one trusted proxy, the rightmost entry is the client as that proxy saw it. With three, the last two entries are your own infrastructure and the third from the right is the target.
Steps to set it up
- Draw the real path from the internet to your process, including CDN, load balancer and ingress.
- Make sure the application only accepts traffic from those proxies, using network rules or security groups.
- Configure your framework or web server with the proxy count or trusted list. Most frameworks have a setting for this, so use it instead of parsing the header by hand.
- Merge repeated X-Forwarded-For header lines before reading them. MDN warns that using only one of several header lines is insufficient.
- Log two fields: the verified client IP, and the raw header value, tagged as client supplied.
- Treat a header that is shorter than expected as untrusted rather than guessing.
What to watch for
A proxy that does not overwrite or append consistently will make your offset wrong. Test with a forged header in staging and confirm the logged address is your test machine, not the forged value.
IP addresses can also be personal data in some jurisdictions, so decide how long you keep them. The retention post linked below covers that angle.
For a sketch of the offset rule in code, take the list, split on commas, and return the entry at position minus N from the end, returning nothing when the list is shorter than N.
Quick check
Is the leftmost X-Forwarded-For value ever safe to use?
Only where spoofing has no negative effect, such as rough analytics. MDN says security uses must rely on addresses added by a trusted proxy.
Should I still log the raw header?
Yes, as a separate field marked unverified. It shows what the client claimed, which helps when you investigate forged requests.
What if my server is reachable without the proxy?
Then no part of the list can be trusted. Restrict inbound traffic so only the proxy can reach the app.
Related: Personal data in logs and structured log fields every line needs.
Safeguard scans your dependencies and code for known issues, including the web frameworks that handle this header. You can try a free scan at gold.safeguard.sh/scan.
Sources
Last reviewed: 19 October 2026.