X-Forwarded-For
request headerrouting & context
The chain of client IP addresses a request passed through on its way via proxies and load balancers.
Behind a proxy, the connection your app sees comes from the proxy, not the user. Each proxy appends the address it received the request from, so the header reads client, proxy1, proxy2. The catch: clients can send a fake X-Forwarded-For, so only the entries added by proxies you control can be trusted.
Syntax
X-Forwarded-For: <client>, <proxy1>, <proxy2>
Defined in RFC 7239 — Forwarded HTTP Extension.
Examples
X-Forwarded-For: 203.0.113.195
one proxy in front of the app
X-Forwarded-For: 203.0.113.195, 198.51.100.17
the client went through two proxies
Good to know
- Read from the right: count back past the proxies you run. The leftmost entry is whatever the client claimed.
- In Express, set
app.set("trust proxy", 1)(the number of proxy hops) soreq.ipuses this header correctly. - The standard replacement is
Forwarded: for=203.0.113.195;proto=https, but X-Forwarded-For remains far more common. - Related de-facto headers:
X-Forwarded-Proto(original scheme) andX-Forwarded-Host(original Host).
Further reading: Fixing ERR_TOO_MANY_REDIRECTS
Who sets X-Forwarded-For
Proxies, load balancers and CDNs add X-Forwarded-For; your app only reads it. In nginx: proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;. Vercel and Cloudflare set it for you, and Vercel also sends the client IP in x-real-ip.
Check it
In the browser, open DevTools → Network, pick a request and look under Request Headers. From the command line, curl's verbose mode prints every header it sends, prefixed with >:
curl -sv https://example.com/ -o /dev/null 2>&1 | grep '^>'
Frequently asked questions
Can I use X-Forwarded-For for rate limiting?
Only the entries added by your own proxies. If you trust the leftmost value, anyone can bypass the limit by sending a different fake IP each time.