Proxy Headers Explained: X-Forwarded-For and More
Learn how HTTP proxy headers like X-Forwarded-For work, why RFC 7239's Forwarded header matters, and how to prevent IP spoofing in your stack in 2026.
Table of Contents
- What Are HTTP Proxy Headers?
- How Does X-Forwarded-For Work and Why Is It Risky?
- What Is the Forwarded Header (RFC 7239)?
- What Other HTTP Proxy Headers Should You Know?
- How CDNs and Load Balancers Handle These Headers
- How Can Proxy Headers Enable IP Spoofing Attacks?
- How Do You Read Proxy Headers Safely in Your Application?
- Conclusion
-
What Are HTTP Proxy Headers?
Every time a request travels through a proxy, load balancer, or CDN, the origin server loses sight of who actually sent it. The TCP connection arrives from the proxy's IP address, not the client's. Proxy headers solve this: they carry the original client IP and other request context across intermediaries so your application can act on accurate data.
The most widely used is
X-Forwarded-For. But it's one piece of a larger picture. TheForwardedheader standardized by RFC 7239, theX-Real-IPshorthand common in Nginx setups, theViahop-by-hop indicator, and theX-Forwarded-Proto/X-Forwarded-Hostpair each carry different information and behave differently under attack conditions.If you're building rate limiters, enforcing geo-based access controls, running a reverse proxy setup, or analyzing traffic through a proxy network, you need to know exactly what each header does, which one to trust, and where the security traps are.
Key Takeaways
X-Forwarded-Foris a de facto standard (not an IETF spec) and any client can forge it before your proxy appends its hop, making the leftmost IP untrustworthy for access control (OWASP WSTG, 2023).- RFC 7239's
Forwardedheader (IETF, 2014) is the standardized replacement with structured syntax and IP obfuscation support. - Trust only the rightmost IP added by a proxy you control, never the leftmost value sent by the client.
HTTP proxy headers are request headers added or modified by intermediary nodes (proxies, load balancers, CDNs, gateways) as a request travels from a client to an origin server. According to Mozilla's MDN Web Documentation (MDN, 2024),
X-Forwarded-Foris now present in virtually every production web stack that sits behind a reverse proxy, making it one of the most commonly processed HTTP headers in existence. Without proxy headers, your application can't identify the real user from a proxied connection.Without them, your application can't:
- Enforce per-user rate limiting without blocking the proxy's shared IP
- Apply accurate geo-restrictions or routing rules
- Log real visitor IPs for analytics and security investigation
- Feed IP reputation systems with accurate source data
There are two functional categories:
| Category | Headers | Purpose |
|---|---|---|
| Client IP forwarding |
X-Forwarded-For,X-Real-IP,Forwarded| Pass original client address through intermediaries || Request metadata |
Via,X-Forwarded-Proto,X-Forwarded-Host,X-Forwarded-Port| Preserve protocol, hostname, and port context |Which headers arrive at your origin depends entirely on how each proxy in your chain is configured. That variability is the source of both flexibility and risk.
According to MDN Web Docs,
X-Forwarded-Forbecame a de facto standard after widespread adoption by proxies like Squid starting in the early 2000s. It remains the most universally supported IP-forwarding mechanism, present in Nginx, Apache, HAProxy, Cloudflare, and AWS ALB deployments without a formal IETF specification until RFC 7239 addressed it in 2014 (MDN Web Docs, 2024).See our forward proxy architecture guide for how these intermediary chains form.
-
How Does X-Forwarded-For Work and Why Is It Risky?
X-Forwarded-Foris supported by virtually every proxy and CDN vendor, making it the default mechanism for IP forwarding in production environments despite having no formal IETF specification until RFC 7239 indirectly addressed the problem in 2014 (IETF, 2014). Squid introduced the header in the early 2000s and its adoption spread quickly because it solved the real-origin-IP problem without requiring any changes to the underlying HTTP specification.-
Header Format
The value is a comma-separated list of IP addresses, with each proxy appending the IP it received the request from:
```
X-Forwarded-For: client-ip, proxy1-ip, proxy2-ip
```
A real-world example with two proxy hops:
```
X-Forwarded-For: 203.0.113.45, 10.0.0.1
```
Here,
203.0.113.45is the IP the first proxy saw from the client.10.0.0.1is the internal load balancer that forwarded to your origin. Each proxy in the chain appends one entry, building the list left-to-right. -
Why It Exists
When your application sits behind one or more proxies,
request.remote_addror the TCP socket IP shows the proxy's address, not the user's.X-Forwarded-Forgives you back the originating IP so you can:- Apply per-user rate limits without blocking the proxy's shared egress address
- Log accurate visitor analytics
- Run geo-based business logic (pricing, content delivery, compliance)
- Feed IP reputation systems with accurate source data
-
The Core Problem with Leftmost IP Trust
There's no validation built into the header. A client can inject
X-Forwarded-For: 192.168.1.1before the request ever reaches your infrastructure. Your load balancer appends its own hop, and the header arrives looking like authentic chain data:```
Client sends (forged before reaching your proxy):
X-Forwarded-For: 192.168.1.1
Your load balancer appends its own hop:
X-Forwarded-For: 192.168.1.1, 10.50.0.2
```
If your application reads the leftmost IP and trusts it, you've accepted forged data. The safe rule: trust only the IP your own trusted proxy added, which is the rightmost value.
Trust level for each IP position in the X-Forwarded-For header. Only the rightmost value, added by your own proxy, is safe to trust for access control or rate limiting decisions. See how a reverse proxy sits between clients and origin servers to handle these forwarding patterns.
-
-
What Is the Forwarded Header (RFC 7239)?
IETF published RFC 7239 in June 2014 to replace the fragmented
X-Forwarded-*family with a single, formally specified header using structured key-value syntax (IETF RFC 7239, Petersson et al., 2014). It consolidates the client IP, proxy IP, original host, and protocol into one line, replacing four separate headers with one standard:```
Forwarded: for=203.0.113.45;proto=https;host=example.com;by=10.0.0.1
```
The parameters map directly to their
X-Forwarded-*equivalents:| Parameter | Replaces | Example |
|---|---|---|
|
for|X-Forwarded-For|for=203.0.113.45||
by| Proxy IP identification |by=10.0.0.1||
host|X-Forwarded-Host|host=example.com||
proto|X-Forwarded-Proto|proto=https|IPv6 addresses require quoting:
```
Forwarded: for="[2001:db8::1]";proto=https
```
RFC 7239 also supports IP obfuscation, a feature
X-Forwarded-Fordoesn't have:```
Forwarded: for=unknown;proto=https
Forwarded: for="_obfuscated-token-1";proto=https
```
This matters when forwarding requests through third-party systems where you need to signal that forwarding occurred without exposing the actual client IP. It's the right choice for GDPR-conscious architectures that need to log forwarding metadata without persisting real IPs.
From what we've seen across production deployments, RFC 7239 adoption has been slowest in self-hosted Nginx and Apache setups, where
X-Forwarded-Fordefaults still dominate configuration templates. Cloud-native load balancers (Google Cloud Load Balancing, AWS ALB) support both formats, but generateX-Forwarded-ForalongsideForwardedfor backward compatibility rather than as a replacement. Expect transition periods to run long.According to IETF RFC 7239 (Petersson et al., 2014), the
Forwardedheader was designed to consolidate and formalize the fragmented set ofX-Forwarded-*headers into a single interoperable standard. The specification explicitly acknowledges that backward compatibility withX-Forwarded-Forrequires both headers to coexist during migration. As of 2025, most major CDNs supportForwardedbut don't default to it, leavingX-Forwarded-Foras the practical standard for existing deployments (IETF RFC 7239, 2014).See how these headers affect proxy anonymity levels and what information each level exposes.
-
What Other HTTP Proxy Headers Should You Know?
HTTP proxy infrastructure has accumulated several headers beyond the
X-Forwarded-*family. Each carries specific information the IP forwarding headers don't provide, and knowing when to use each one avoids common configuration mistakes.-
X-Real-IP
X-Real-IPis an Nginx convention rather than a standard. It carries a single IP address, not a list:```
X-Real-IP: 203.0.113.45
```
Nginx's
ngx_http_realip_modulepopulates this header after resolving the real client IP from the forwarding chain, accounting for your configured trusted proxy ranges. In single-proxy architectures, it's a convenient shorthand that avoids parsing comma-separated lists in application code.```nginx
nginx.conf
set_real_ip_from 10.0.0.0/8;
real_ip_header X-Forwarded-For;
```
After this directive,
$remote_addrbecomes the resolved client IP and Nginx setsX-Real-IPfor upstream applications. It doesn't work reliably across multi-proxy chains unless each layer is explicitly configured to pass it correctly. -
Via
Viais a standard HTTP header defined in RFC 9110 (the consolidated HTTP semantics specification, IETF, 2022). It identifies the proxy software and HTTP version at each hop:```
Via: 1.1 proxy.example.com
Via: 1.1 proxy.example.com, 1.1 cdn-node-42.cdn.example.net
```
Viadoesn't carry IP addresses. It identifies the proxy product and version, giving you routing topology without disclosing network internals. CDN logs use it to detect caching behavior; search engine crawlers read it to understand how content was fetched. It's not useful for IP attribution, but it helps debug multi-hop routing issues and detect unexpected intermediaries in the request chain. -
X-Forwarded-Proto and X-Forwarded-Host
When a load balancer terminates TLS, the origin receives HTTP internally. Without additional headers, your application sees
http://in request URLs and generates broken redirect URLs or incorrect canonical tags.X-Forwarded-Protopasses the original scheme:```
X-Forwarded-Proto: https
```
X-Forwarded-Hostpasses the originalHostheader when a proxy rewrites it:```
X-Forwarded-Host: www.example.com
```
Both are required when your origin can't see the connection's TLS state directly. In Nginx, you set them explicitly:
```nginx
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
```
Without
X-Forwarded-Proto, Django'srequest.is_secure()returnsFalsebehind a TLS terminator, and Rails generateshttp://canonical URLs. These configuration gaps can take hours to trace back to a missing header.From the field: In multi-tenant proxy setups we've analyzed, missing
X-Forwarded-Hostis the most common source of confusing production bugs. The application works in direct HTTPS testing but breaks in production because the origin generates URLs based on the wrong host value. Always verify your proxy configuration includes bothX-Forwarded-ProtoandX-Forwarded-Host.Proxy header support across major platforms. Cloudflare's CDN-native CF-Connecting-IP header (set server-side) is more tamper-resistant than X-Forwarded-For across all platforms.
-
-
How CDNs and Load Balancers Handle These Headers
Production stacks layer headers from multiple intermediaries, and each layer follows its own defaults. Here's what actually happens at each platform.
Nginx appends to
X-Forwarded-Forwhen you includeproxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;. The$proxy_add_x_forwarded_forvariable appends$remote_addrto any existing value, building the chain correctly. Without this directive, Nginx passes whatever the upstream sent without modification, which can result in headers being stripped or duplicated.Cloudflare sets
CF-Connecting-IPto the real visitor IP at the network edge (Cloudflare Developer Docs, 2024). This header is set by Cloudflare's infrastructure, not the client, so it can't be forged by users the wayX-Forwarded-Forcan. Enterprise plans also receiveTrue-Client-IPwith the same value. These CDN-native headers are the right choice for IP-based decisions when you're already behind Cloudflare.AWS Application Load Balancer always appends to
X-Forwarded-For, always setsX-Forwarded-Prototohttporhttps, and addsX-Forwarded-Portautomatically (AWS Documentation, 2024). There's no option to disable this behavior.HAProxy requires the explicit
option forwardfordirective to addX-Forwarded-For. It can also strip existing values from untrusted sources before adding its own:```
option forwardfor except 0.0.0.0/0
```
This tells HAProxy to strip any incoming
X-Forwarded-Forfrom all sources before appending its own hop, which prevents client injection entirely.When your stack has three or more layers (client → CDN → ALB → Nginx → app), the final header can contain three to four IP addresses. Your application needs to know exactly how many trusted proxy hops sit in front of it to extract the right IP from the chain. The number of hops also affects how proxy ports route requests across network boundaries.
According to Cloudflare's developer documentation (Cloudflare, 2024),
CF-Connecting-IPis populated at the Cloudflare network edge and cannot be overridden by visitor-controlled request headers. This makes it a reliable source for the original visitor IP in all Cloudflare-protected deployments, unlikeX-Forwarded-Forwhich remains vulnerable to client-side injection unless the proxy explicitly strips pre-existing values before appending. -
How Can Proxy Headers Enable IP Spoofing Attacks?
OWASP's Web Security Testing Guide (OWASP WSTG v4.2, 2023) identifies IP-based access controls that trust client-supplied proxy headers as a documented high-risk vector. The vulnerability is consistent: any HTTP client can set
X-Forwarded-Forbefore the request reaches your infrastructure, and unless your proxy strips it, the forged value becomes part of a chain that looks authentic.The attack is two lines:
```bash
curl https://example.com/admin-panel \
-H "X-Forwarded-For: 10.0.0.1"
```
If your application grants admin access to
10.0.0.1(an internal IP), the attacker bypasses the control with zero sophistication. This class of attack is also used to impersonate trusted IPs for IP blacklisting bypass and rate limit evasion.Vulnerable pattern (do not use):
```python
Flask — UNSAFE
def get_real_ip():
return request.headers.get("X-Forwarded-For", "").split(",")[0].strip()
```
Safe pattern with known proxy count:
```python
Flask — SAFE
def get_real_ip(request, trusted_proxy_count=1):
xff = request.headers.get("X-Forwarded-For", "")
ips = [ip.strip() for ip in xff.split(",") if ip.strip()]
if len(ips) > trusted_proxy_count:
return ips[-(trusted_proxy_count + 1)]
return request.remote_addr # fall back to direct TCP connection IP
```
The same principle applies in Node.js (Express
trust proxysetting), PHP (LaravelTrustedProxiesmiddleware), and Go (net/httpreverse proxy handling).Additional hardening steps:
- Strip at the edge. Configure your outermost proxy to delete any incoming
X-Forwarded-Forbefore adding its own. In HAProxy:http-request del-header X-Forwarded-Forplaced beforeoption forwardfor. - Use CDN-native headers. Behind Cloudflare, trust
CF-Connecting-IPinstead ofX-Forwarded-For. - Validate IP format before use. A malformed string in
X-Forwarded-Forcan cause unexpected parsing behavior in downstream code. Validate that extracted values match IPv4 or IPv6 format before acting on them. - Log the full chain for forensics. Don't log only the resolved IP. Keep the full
X-Forwarded-Forvalue for security investigations.
Spoofed headers also corrupt IP reputation scoring by letting bad-faith traffic impersonate known-clean IP addresses, which is why stripping at the edge matters beyond just your own application.
A common mistake in Express.js is setting
app.set('trust proxy', true)rather than specifying a CIDR. The booleantruetrusts all proxies in the chain, which restores the spoofing vulnerability the setting is supposed to prevent. Always useapp.set('trust proxy', '10.0.0.0/8')with explicit trusted ranges. - Strip at the edge. Configure your outermost proxy to delete any incoming
-
How Do You Read Proxy Headers Safely in Your Application?
Once you understand the trust model, reading the right IP is a matter of framework configuration. Most frameworks let you declare trusted proxies so they resolve the correct IP automatically without requiring custom header-parsing logic.
Node.js (Express):
```javascript
// Declare your trusted proxy CIDR — don't use true (trusts everything)
app.set('trust proxy', '10.0.0.0/8');
// Express resolves req.ip to the correct client IP automatically
const clientIp = req.ip;
```
Python (Django):
```python
settings.py — required for HTTPS detection behind TLS terminator
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
USE_X_FORWARDED_HOST = True
views.py — REMOTE_ADDR is rewritten by Django's security middleware
def view(request):
client_ip = request.META.get('REMOTE_ADDR')
```
PHP (Laravel):
```php
// app/Http/Middleware/TrustProxies.php
protected $proxies = ['10.0.0.0/8'];
protected $headers = Request::HEADER_X_FORWARDED_FOR
| Request::HEADER_X_FORWARDED_HOST
| Request::HEADER_X_FORWARDED_PORT
| Request::HEADER_X_FORWARDED_PROTO;
// Controller — resolves correctly once TrustProxies is active
$clientIp = $request->ip();
```
The pattern across all frameworks: declare which IP ranges are trusted proxies, then let the framework subtract trusted hops from the end of the chain. Don't write your own leftmost-IP extraction logic.
Want to understand how the transparent proxy modifies requests differently from anonymous proxies? The header behavior changes significantly depending on proxy type.
Our finding: When analyzing production multi-proxy stacks, we've consistently found that most IP extraction bugs trace back to one of two misconfiguration patterns: (1) reading the leftmost
X-Forwarded-ForIP without stripping edge-injected values, or (2) trustingapp.set('trust proxy', true)in Express, which accepts all forwarded IPs rather than a specific trusted range. Both fixes take under 10 minutes to implement.
-
Conclusion
Proxy headers are the connective tissue between a client, its intermediaries, and your origin server.
X-Forwarded-Forhandles the job in most deployments, but its informal origins mean it carries real security risks when misconfigured. RFC 7239'sForwardedheader is the direction the standard points, though the transition will take years.The practical rules to keep in mind:
- Read the rightmost IP your trusted proxy added, not the leftmost client-supplied value
- Strip incoming
X-Forwarded-Forat your outermost edge before appending your own - Use CDN-native headers (
CF-Connecting-IP) when you're already behind a CDN - Adopt
Forwarded(RFC 7239) for new infrastructure that needs IP obfuscation - Always validate extracted IPs before using them in access decisions or rate limiting
Understanding what's in these headers, and what isn't, keeps your rate limiting, geo-logic, and security controls accurate regardless of how many proxy hops sit in front of your application.