X-Forwarded-For Header: Format, Forwarded Comparison and Security

X-Forwarded-For (XFF) is the commonly deployed HTTP request header for carrying a chain of client and proxy IP addresses. A header is extra information attached to a web request. A proxy receives the request and passes it on to another server; each such step is called a hop. The standardized Forwarded header defined by RFC 7239 is an alternative that labels each piece of information with a name. It can carry for, by, host, and proto values for each proxy hop.

Neither header proves a client's address. Accept forwarding metadata only from configured trusted proxies that remove or replace values received from untrusted clients. Parse the chain only in relation to the proxy that delivered the request.

What is the X-Forwarded-For format?

The common format is a comma-separated list:

X-Forwarded-For: <client>, <proxy-1>, <proxy-2>

A proxy that preserves the header appends the address of the connection it received. The leftmost value is the claimed original client, and values added by later proxies appear to its right. The system connected directly to the application is its immediate peer. That peer's address comes from the network connection and may not appear in the header.

X-Forwarded-For: 203.0.113.24
X-Forwarded-For: 203.0.113.24, 198.51.100.7
X-Forwarded-For: 203.0.113.24, 198.51.100.7, 192.0.2.43

Header names are case-insensitive, and optional whitespace commonly follows each comma. Repeated header fields and unusual whitespace should be handled by the HTTP server or a well-tested forwarding-header library, not by splitting an arbitrary string in application code.

What is the standardized Forwarded header?

RFC 7239 defines Forwarded as a standardized alternative to the separate X-Forwarded-* headers. A comma separates elements added by successive proxies. Semicolons separate parameters that belong to the same element.

X-Forwarded-For: 203.0.113.24, 198.51.100.7
Forwarded: for=203.0.113.24;proto=https, for=198.51.100.7
  • for= identifies the node that connected to the proxy.
  • by= identifies the proxy's client-facing interface.
  • host= preserves the host received by the proxy.
  • proto= preserves the incoming URI scheme, usually http or https.

Parameters in one element belong to the same proxy hop. Separate X-Forwarded-For, X-Forwarded-Host, and X-Forwarded-Proto lists cannot always preserve that correlation. The application's immediate peer still comes from the network connection and normally is not the last for= value.

How do X-Forwarded-For and Forwarded compare?

Property X-Forwarded-For Forwarded
Specification De facto convention RFC 7239 Standards Track
Representation Comma-separated address list Comma-separated proxy elements with parameters
Information Primarily client and proxy addresses for, by, host, proto, and extensions
Hop correlation Only within the address list Parameters in an element belong together
Non-address identifiers No standard representation Supports unknown and obfuscated values
Authenticity None None

Which header should I use?

Use Forwarded when you control the complete proxy path and every component parses RFC 7239 correctly. Keep XFF when it is the documented output of your content delivery network (CDN), which serves content through distributed servers, or your load balancer, which distributes requests among servers. Also keep XFF when your server's trusted-proxy support expects it. Forwarded is more expressive; XFF usually has wider compatibility. The trust-boundary configuration matters more than the header name.

Choose one agreed header family at the application boundary. Do not accept whichever header happens to be present.

How are IP addresses, ports, and non-IP identifiers represented?

Address forms in X-Forwarded-For

A port is a number used with an IP address to distinguish an application or exchange of data. For example, HTTPS commonly uses destination port 443. A protocol defines how systems exchange information; TCP and UDP are transport protocols used to carry traffic. An IPv4 address is normally written in dotted-decimal form. IPv6 addresses contain colons, which makes an address-and-port value ambiguous without brackets:

X-Forwarded-For: 203.0.113.24
X-Forwarded-For: 2001:db8::1234
X-Forwarded-For: [2001:db8::1234]:4711, 198.51.100.7

X-Forwarded-For is a de facto header rather than an RFC-defined format, so port and IPv6 conventions vary between products. Use the exact syntax documented by your proxy and framework. Do not split an IPv6 value at every colon.

Do not assume that this header preserves the visitor's source port. The source port on the connection accepted by your application belongs to the nearest proxy. If an investigation needs the visitor's public source port, capture it at the Internet-facing edge or use a documented, sanitized provider field or edge log.

Node identifiers in Forwarded

Forwarded: for=203.0.113.24
Forwarded: for="[2001:db8::1234]"
Forwarded: for="203.0.113.24:4711"
Forwarded: for="[2001:db8::1234]:4711"
Forwarded: for=unknown
Forwarded: for=_hidden

RFC 7239 encloses IPv6 addresses in square brackets. IPv6 values and any node identifier with a port must be quoted. unknown means the preceding node is not known. A value beginning with an underscore is an obfuscated identifier, not an IP address. A parser should recognize these valid forms, but client-IP derivation should return unavailable when the selected for= value is not an IP address.

Which X-Forwarded-For address is the real client?

A secure parser starts with the application's immediate peer, then walks the forwarding chain from right to left. It continues only while each hop is a proxy that the application is configured to trust. The first address outside that trusted chain is the derived client IP.

Header: 203.0.113.24, 198.51.100.7
Immediate peer: 192.0.2.43
Walk: 192.0.2.43 (trusted) ← 198.51.100.7 (trusted) ← 203.0.113.24
Derived client: 203.0.113.24

This result is valid only if the configured topology is true. If the immediate peer is not trusted, ignore the header. Prefer explicit trusted proxy CIDRs, including the documented egress ranges of a trusted third-party service. Use a fixed hop count only when every request follows an invariant path through exactly that many proxies and no direct or shorter route can reach the application. Otherwise an attacker-controlled value can move into the position treated as trusted. Once the walk reaches the first address outside the trusted set, stop. Entries farther left are untrusted claims and must not influence the derived client IP.

Does the same walk work for Forwarded?

Yes, but parse proxy elements instead of a plain address list:

Header: for=203.0.113.24;proto=https, for=198.51.100.7
Immediate peer: 192.0.2.43
Walk: 192.0.2.43 (trusted) ← 198.51.100.7 (trusted) ← 203.0.113.24
Derived client: 203.0.113.24

Stop if a required element is malformed, missing, unknown, obfuscated, or inconsistent with the configured topology. Do not skip it and continue farther left. A by= value may help with logging or consistency checks, but it does not authenticate the chain. Because the header is optional, its shape does not prove that every intermediary added an element.

What if X-Forwarded-For is missing?

Use the immediate peer address from the network connection. If a trusted proxy was supposed to add the header, treat its absence as a configuration or logging problem. Do not fall back to a different client-supplied header.

What if there are multiple X-Forwarded-For header lines?

Use the ordered, combined list produced by a server or framework that documents repeated-field handling. If your stack exposes separate lines and cannot preserve their order safely, reject or ignore them. Do not select an arbitrary first or last line.

What if Forwarded and X-Forwarded-For are both present?

Do not merge the chains or dynamically trust whichever header is present. A client could supply one family while the edge sanitizes only the other. Configure the application to use one agreed header family. At the first trusted component you control, verify the immediate peer, remove untrusted inbound Forwarded, X-Forwarded-*, X-Real-IP, and similar fields, then generate the agreed forwarding header used inside your network.

If both families unexpectedly reach the application, log the conflict and ignore or reject the forwarding metadata rather than silently combining it.

How does X-Forwarded-For spoofing happen?

A client can make this request directly:

X-Forwarded-For: 198.51.100.50

If the application always accepts the leftmost value, the client chooses the address used by audit logs, IP allowlists, rate limits, fraud rules, and abuse controls. Adding another value to the left is enough to change that result when the application has no trust boundary.

Unsafe

Use the first header value for every request, regardless of the immediate peer.

Safer

Accept forwarding data only from known proxies that sanitize incoming headers.

Restrict direct access to the application where possible. Configure the trusted edge to overwrite untrusted incoming values, and keep trusted proxy ranges current. In a multi-CDN chain, one provider's recognizable header is not trustworthy merely because it has the expected name; the edge you control must set or authenticate it.

Forwarded affects more than client-IP extraction. Frameworks may use its host= and proto= parameters when reconstructing the external request URL. Untrusted values can then affect absolute redirects, generated links, origin checks, and other host- or scheme-dependent behavior. Sanitize the whole header family even if the application initially needs only an IP address.

How should I parse and log forwarding headers?

  • Use the web server or framework's trusted-proxy support instead of a custom string parser.
  • Prefer explicit trusted proxy CIDRs. Use a fixed hop count only for an invariant path with no direct or shorter origin route.
  • Bound the header length, number of elements, and parameters per element before parsing untrusted input.
  • Reject malformed addresses rather than interpreting a partial value.
  • Normalize an accepted IPv4 or IPv6 address before comparing or storing it.
  • Log the derived client IP, immediate peer, and source header. Keep a bounded, safely encoded original value separately only when needed; treat it as attacker-controlled and minimize access and retention.
  • Do not use the derived address as proof of a person; it may still be a VPN, proxy, or CGNAT gateway.

Do not parse Forwarded with independent split(",") and split(";") calls. A conforming parser must preserve repeated-field ordering and handle quoted strings, escapes, case-insensitive parameter names, and extension parameters. Reject a duplicate parameter within one element. Parse and unquote a value before normalizing an IP address. Never fall back from malformed Forwarded data to unsanitized XFF data.

Minimize what proxies disclose. Avoid by=, internal addresses, and source ports unless they serve a defined operational purpose. They can expose internal topology and add unnecessary personal data to logs. Never copy a request's Forwarded field into a response.

For the complete workflow around CDNs, load balancers, reverse proxies, and non-HTTP services, see How to Determine the Client IP Behind a CDN or Reverse Proxy.

Signal Shape Trust requirement
X-Real-IP Usually one address rather than a proxy list. Trust only when a known proxy overwrites it.
Provider header Examples include CF-Connecting-IP and Fastly-Client-IP. Accept only from the expected provider and only after confirming that the edge overwrites client-supplied values. Fastly requires explicit Varnish Configuration Language (VCL) sanitization.
PROXY protocol Transport preamble before the application protocol. Version 1 describes TCP; version 2 can carry stream or datagram metadata. Accept only from explicit trusted load balancer addresses.
Via Identifies HTTP intermediaries, not the original client. Do not use it as a substitute for a client-IP chain.

Sources and further reading