2026-10-01 · 7 min read
- http
- proxy
- security
RFC 7239 defines the Forwarded header as a list of hop elements. Each element can carry for, by, host, and proto. The header is how an application learns the client address and the original scheme when a reverse proxy terminated the connection.
The caller can set the same header. Until a trusted proxy replaces or annotates it, none of the values are evidence. Rate limits, audit logs, and IP allow lists that read the raw header will enforce the attacker's choice.
Strip at the first trusted proxy
The proxy that accepts public traffic should delete inbound Forwarded and X-Forwarded-For, then set one element from the socket it accepted. The application behind that proxy can then trust the header. Additional internal proxies may append their own element, and the application should know how many trusted appends to expect.
If you cannot strip, do not use the leftmost address. Leftmost is the original client only after every untrusted value has been removed. Otherwise leftmost is the value the client chose, and the address your proxy observed is the element that proxy added.
- Delete inbound forwarding headers on the public listener.
- Write for from the accepted connection, not from a header.
- Set proto from the listener's scheme.
- Document the hop count the application trusts.
Parse the grammar before you store an IP
Elements are separated by commas. Parameters inside an element are separated by semicolons. IPv6 addresses are quoted and enclosed in brackets. A comma inside those quotes does not start a new element. unknown and tokens that begin with an underscore are obfuscated identifiers, not addresses to geolocate.
X-Forwarded-For is only a comma-separated address list. It has no proto or host parameter. A framework can prefer it even when your proxy wrote Forwarded. Configure both headers in one place so they cannot disagree.
Treat host and proto as authority, not decoration
Applications build redirects, password-reset links, and cookie flags from the original scheme and host. A forged proto or host is a request-smuggling and password-reset problem, not only a bad log line. After the edge proxy sets them, ignore client-supplied copies on internal hops.
Keep the proxy's own name in by when you need to debug which hop wrote the element. Do not require by for authorization decisions. The trust comes from the network position of the proxy, not from a string inside the header.
Test the chain you will actually deploy
Send a request directly to the app, through the edge proxy, and through the edge proxy plus one internal hop. Also send a request that already contains a fake for, a quoted IPv6, and a second header line. The stored client address should match the socket the edge proxy saw in every case.
Include an http listener and an https listener if you terminate TLS at the proxy. proto in the application should follow the listener, not the header the client typed.
Review headers in local tools
Use Flashman's URL tool on the host and scheme you will publish, the JSON formatter for parsed elements, diff for the header before and after the proxy, and the regex tester to confirm that quoted IPv6 addresses stay inside one element.
Write the authoritative header name and the trusted hop count in the runbook next to the proxy config. The next incident should start from that sentence.