What is the Forwarded header?
The Forwarded request header, defined in RFC 7239, carries the information a reverse proxy would otherwise destroy by terminating the connection: who connected to it, on which interface, over which protocol, and with which Host. It is the standardized replacement for X-Forwarded-For, X-Forwarded-Proto and X-Forwarded-Host, folding all three into one field.
The value is a comma-separated list of elements, one per proxy, each a set of semicolon-separated name=value pairs. Four parameters are defined:
| Parameter | Meaning |
|---|---|
for |
The node that made the request to the proxy, usually the client |
by |
The interface on the proxy where the request came in |
host |
The Host header as the proxy received it |
proto |
The scheme of the incoming request, such as http or https |
A node identifier is an IP address with an optional port, the literal unknown, or an obfuscated identifier starting with an underscore (_hidden) that a proxy uses to avoid leaking internal addresses. Because : is not legal in a token, an IPv6 address or any value with a port must be a quoted string, and IPv6 goes in square brackets: for="[2001:db8:cafe::17]:4711".
API Usage & Best Practices
- Read it from the right, and only as far as you trust. Each proxy appends its element, so the list is ordered from the first proxy to the last. Anything a client sent arrives at the front of the list untouched. Walk from the rightmost element, skip the ones your own proxies added, and take the first one you did not add. RFC 7239 Section 8.1 is explicit that the header “cannot be relied upon to be correct”, because any node on the path, the client included, can write to it.
- Never trust it on a server that is reachable directly. If the origin accepts connections that bypass your proxy, a client can send a
Forwardedheader of its choosing and none of it is trustworthy. Lock the origin down to the proxy’s addresses first. - Use
protoandhostto build absolute URLs. An application behind a TLS-terminating proxy sees plain HTTP on its own socket. Building aLocationor a pagination link from that produceshttp://URLs. Readprotoandhostfrom the element your trusted proxy added instead. - Normalize it at the edge. If you control the first proxy, have it strip any inbound
Forwarded(andX-Forwarded-*) from the client and write a fresh one. Everything downstream then only has to trust one hop. - Do not reflect it in responses. The RFC says origins and intermediaries should not copy it into a response, because it exposes the proxy chain and client addresses. It is privacy-sensitive by design.
- Prefer it for new infrastructure, but expect
X-Forwarded-For. Most proxies, load balancers and frameworks still default to theX-Forwarded-*headers. If you parse both, decide which one wins and configure the edge to set only that one, otherwise a client can supply whichever your code does not overwrite.
Examples
A request that passed through two proxies, the first of which recorded an IPv6 client:
GET /v1/orders HTTP/1.1Host: internal.example.comForwarded: for="[2001:db8:cafe::17]:4711";proto=https;host=api.example.com, for=198.51.100.17;by=203.0.113.43An edge proxy hiding its internal topology with obfuscated identifiers:
GET /v1/orders HTTP/1.1Host: internal.example.comForwarded: for=192.0.2.60;proto=https;by=_edge01