Back to HTTP Headers

Forwarded proxy request

The standardised header for passing client IP and protocol info through proxies — the RFC 7239 replacement for X-Forwarded-For.

What it does

Forwarded passes information about the original client and the proxy chain to the origin server. When a request travels through a reverse proxy or load balancer, the original client IP, protocol, and host are lost — the server only sees the proxy's connection. Forwarded preserves that context.

It's the RFC 7239 (2014) standardisation of the older, non-standard X-Forwarded-For, X-Forwarded-Host, and X-Forwarded-Proto headers that everyone was using inconsistently before.

Syntax

Forwarded: for=<client>;by=<proxy>;host=<host>;proto=<protocol>
Forwarded: for=<client1>, for=<client2>

Each proxy appends a new Forwarded value (or a new entry in the list). The leftmost entry represents the original client; each subsequent entry is another proxy hop.

Examples:

Forwarded: for=192.0.2.60;proto=http;by=203.0.113.43;host=example.com
Forwarded: for="[2001:db8::cafe]"
Forwarded: for=192.0.2.60, for=203.0.113.43
Forwarded: for=_hidden;proto=https

Parameters

Parameter Meaning
for The client that made the request (IP address, or obfuscated identifier)
by The proxy that received the request from the client
host The Host header the client sent to the proxy
proto The protocol the client used (http or https)

All parameters are optional per entry, but for is by far the most commonly used.

IP address formats

IPv4: for=192.0.2.60 IPv6 (must be quoted): for="[2001:db8::cafe]" With port: for="192.0.2.60:4711" or for="[2001:db8::cafe]:4711" Obfuscated (privacy): for=_hidden or for=_obf4321 Unknown: for=unknown

Forwarded vs X-Forwarded-For

X-Forwarded-For came first (de facto standard, no RFC), Forwarded is the official standardisation:

X-Forwarded-For Forwarded
Standard No (de facto) Yes (RFC 7239)
Carries IP only (comma list) IP + protocol + host + proxy identity
IPv6 Inconsistent Defined
Ports Not supported Supported
Multiple proxies Comma in one header Multiple header instances or comma list
Adoption Universal Growing

In practice, X-Forwarded-For is still more widely used because it predates Forwarded by many years and is supported everywhere. Most frameworks read both. When in doubt, set both.

Reading the real client IP

// Laravel — reads X-Forwarded-For by default when trusted proxies are configured
// In config/trustedproxy.php or App\Http\Middleware\TrustProxies

// Reading Forwarded header manually:
function getClientIp(Request $request): string
{
    $forwarded = $request->header('Forwarded');
    if ($forwarded) {
        // Parse first "for=" value from leftmost entry
        if (preg_match('/for=([^;,\s]+)/i', $forwarded, $m)) {
            return trim($m[1], '"[]');
        }
    }
    return $request->ip(); // Falls back to REMOTE_ADDR
}

Always validate the proxy IP before trusting Forwarded. If you accept Forwarded from any IP, a user can spoof their IP by setting the header themselves. Only trust it from known proxy IPs (your load balancer, CDN IP ranges).

Multi-proxy chain

Each proxy appends its own entry:

# Client → Proxy A → Proxy B → Origin

Origin sees:
Forwarded: for=192.0.2.1;proto=https, for=10.0.0.1;by=10.0.0.2

First entry = original client. Last entry = most recent proxy. Reading from left to right reconstructs the path.

Security: IP spoofing

The single biggest gotcha with Forwarded (and X-Forwarded-For):

An attacker can set Forwarded: for=1.2.3.4 on their own request. If your app reads this naively without validating the proxy, the attacker controls what IP your app sees. This breaks IP-based rate limiting, geo-blocking, audit logging, and security decisions.

The fix: only trust Forwarded from requests that arrive from your known proxy IP range. In Laravel:

// config/trustedproxies.php
'proxies' => ['10.0.0.0/8', '172.16.0.0/12'], // Your load balancer ranges

// Or trust Cloudflare's published IP ranges

Real-world examples

Single proxy (load balancer):

GET /api/user HTTP/1.1
Host: example.com
Forwarded: for=203.0.113.5;proto=https;host=example.com

Multi-hop (CDN → load balancer → app):

Forwarded: for=203.0.113.5;proto=https, for=10.10.0.1;by=10.10.0.2

IPv6 client:

Forwarded: for="[2001:db8::1]";proto=https

FAQ

Should I use Forwarded or X-Forwarded-For?

Both for maximum compatibility. Read Forwarded first, fall back to X-Forwarded-For. When setting (in your own proxy), prefer Forwarded but also set X-Forwarded-For for backward compatibility with apps that don't support the newer header yet.

What does for=_hidden mean?

It's a privacy-preserving obfuscation — the proxy knows the client IP but doesn't want to expose it. _hidden or any _-prefixed identifier is a valid obfuscated node identifier per RFC 7239. You know a proxy hop existed but not who the client was.

Fun fact

X-Forwarded-For was invented by Squid proxy developers around 2000 with no standardisation process whatsoever — just a useful header they needed. It spread virally across the entire web infrastructure industry over the next decade, implemented differently by every load balancer and CDN vendor (some comma-separate, some add new header instances, some use different capitalisations). RFC 7239 was finally published in 2014 to standardise the concept properly. By then X-Forwarded-For was so entrenched that Forwarded has been playing catch-up ever since — a 14-year head start is hard to overcome even when your spec is cleaner.

Related Headers