IETF RFC 2616 Section 4.2 allows a request to contain multiple headers with the same field-name as long as chronological order of insertion is preserved and their values can be converted into single header with a comma-separated list of values.
message-header fields with the same field-name MAY be present in a
message if and only if the entire field-value for that header field is
defined as a comma-separated list [i.e., #(values)]. It MUST be
possible to combine the multiple header fields into one “field-name:
field-value” pair, without changing the semantics of the message, by
appending each subsequent field-value to the first, each separated by
a comma. The order in which header fields with the same field-name are
received is therefore significant to the interpretation of the
combined field value, and thus a proxy MUST NOT change the order of
these field values when a message is forwarded. F5 will not overwrite
any existing X-Forwarded-For. Nor will it concatenate existing
X-Forwarded-For into a single comma-separated value. Instead, it will
insert an additional X-Forwarded-For at the tail end of the
But what about an environment with multiple clients, proxies, CDNs, traffic-managers, servers that engage in manipulation of the
X-Forwarded-For collection ?
There would seem to be an advantage to enforcing a uniform practice. But what is the best practice ?
F5 BIG-IP default http profile insert header accumulates an additional
X-Forwarded-For at the end of a request’s pre-existing collection of XFF headers, preserving order.
AWS ELB encourages consolidation of an incoming request’s multiple
X-Forwarded-For into a single header containing a comma-delimited list of XFF IPs, plus the user host address, preserving order.
Other devices may employ other variations.
Does there exist an agreed-upon recommendation or de facto standard for heterogeneous environments ?
Further, is any timestamp data provided that would allow code to definitively sort
X-Forwarded-For headers in chronological order of addition for the case where previous manipulations of XFF headers are suspect.
Yes, there’s a standard: Don’t use X-Forwarded-For at all.
RFC 7239 defines the Forwarded header, which has rather different semantics from X-Forwarded-For, and new implementations ought to be using it. Unfortunately it suffers from the same problem you have identified with X-Forwarded-For here: it may be defined twice in a request or contain a comma separated list of values. Proxies are also allowed to delete it entirely.
And yes, there’s a best practice: Use a different header name internally.
Remember that X-Forwarded-For and its replacement Forwarded contain untrusted input. It is trivial for a client to put whatever they want in such a header. If you really need to know the public IP address of whatever connected to your server, stick it in a different header. For instance, CloudFlare uses CF-Connecting-IP for this purpose. I’ve also seen Client-IP and X-Real-IP used in nginx (where you can define anything you want). Whatever name is used, your load balancers should be sending the requester’s IP address in some header other than X-Forwarded-For or Forwarded.
This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.