HTTP

Header

X-Forwarded-For

Learn how X-Forwarded-For identifies the original client IP when requests pass through proxies or load balancers. Essential for logging and security.

2 min read intermediate Try in Playground

TL;DR: Identifies the original client IP address when requests pass through proxies or load balancers. Parse carefully as it can be spoofed by clients.

What is X-Forwarded-For?

The X-Forwarded-For (XFF) header identifies the originating IP address of a client connecting to a web server through an HTTP proxy or load balancer.

Syntax

X-Forwarded-For: <client>, <proxy1>, <proxy2>
```text

The leftmost value is typically the original client IP, and each proxy appends the address it received from upstream.

## Example

```http
X-Forwarded-For: 203.0.113.195, 70.41.3.18, 150.172.238.178

Security Model

Never trust raw X-Forwarded-For from arbitrary clients. Attackers can send fake headers.

Safe handling pattern:

  • maintain a list of trusted proxy IP ranges
  • process chain from right to left
  • stop at first untrusted hop

Implementation

// Express: trust only your known edge proxy CIDRs/IPs
app.set('trust proxy', ['10.0.0.0/8', '192.168.0.0/16'])

app.get('/request-ip', (req, res) => {
  // req.ip now accounts for trusted proxy chain
  res.json({ ip: req.ip })
})

Standard Alternative

The standards-based equivalent is Forwarded, but XFF still has broader operational support.

Common Mistakes

  • Taking the first IP blindly without trust-proxy configuration
  • Logging only socket remote address behind load balancers
  • Forgetting IPv6 normalization in IP allow/deny logic

IP Spoofing and Trusted Proxy Configuration

The most dangerous mistake with X-Forwarded-For is reading the leftmost IP address without validating the proxy chain. An attacker can send X-Forwarded-For: 1.2.3.4 in a direct request to your origin server, making your application believe the request came from IP 1.2.3.4. This breaks IP-based rate limiting, geo-blocking, and audit logging.

The correct approach is to process the header from right to left, starting from the rightmost IP (added by your trusted edge proxy) and working backwards until you reach the first IP that was not added by a trusted proxy. That IP is the real client address.

Most frameworks provide a trust proxy configuration that automates this. In Express.js, app.set('trust proxy', 'loopback, 10.0.0.0/8') tells the framework which proxy IP ranges to trust, and req.ip then returns the correct client IP. In Django, TRUSTED_PROXY_HEADERS and TRUSTED_PROXY_IPS serve the same purpose. In nginx, set_real_ip_from combined with real_ip_header X-Forwarded-For extracts the real IP before passing the request upstream.

When logging client IPs for security purposes, always log the full X-Forwarded-For chain alongside the socket remote address. This gives you the complete picture for forensic analysis while still allowing you to identify the real client IP for rate limiting and access control. Never log only the forwarded IP without the socket address, as the socket address is the only value that cannot be spoofed by the client.

Frequently Asked Questions

What is X-Forwarded-For?

X-Forwarded-For identifies the original client IP when requests pass through proxies. Each proxy appends the previous IP. The leftmost IP is typically the original client.

How do I get the real client IP?

Parse X-Forwarded-For from right to left, stopping at the first untrusted IP. Only trust IPs added by your known proxies. The leftmost may be spoofed.

Can X-Forwarded-For be spoofed?

Yes, clients can send fake X-Forwarded-For headers. Only trust values added by your proxies. Configure your app to know which proxies to trust.

Should I use X-Forwarded-For or Forwarded?

Forwarded is the standard replacement but X-Forwarded-For has wider support. Many systems accept both. Check your infrastructure compatibility.

Keep Learning