X-Forwarded-For

The originating IP address of a client request.

Fastly reads this header from requests and writes it into requests. It is defined by an external standard.

For VCL services, Fastly will add or append X-Forwarded-For headers, as follows:

  • The client IP, added when the request is first seen by Fastly.
  • The Fastly edge POP IP – added when the request is received by the shield POP, if the service has shielding enabled.

IMPORTANT: Fastly modifies the XFF header only on secure connections.

  • If the connection from the client is not secure (not TLS) we will not add the client IP.
  • When shielding, if the origin selected on as the designated backend for the current request is not configured to use TLS, we will not add the edge POP IP.

Fastly does not modify this header in Compute services, but you can do so in your own edge code by looking up the client IP using the appropriate SDK method.

Example

Consider a request originally from a user with IP 1.1.1.1, which first passes though a non-Fastly proxy at 2.2.2.2, and is then received by a Fastly POP at 3.3.3.3. The service has shielding, and shields to a second Fastly POP at 4.4.4.4. That second Fastly POP finally forwards the request to an origin server at 5.5.5.5.

When the request is first seen by Fastly it will likely carry the header X-Forwared-For: 1.1.1.1 (though it may not: we have no control over the behavior of downstream proxies). Provided that the request is received by Fastly over TLS, we will append the IP of the client, which from our perspective is 2.2.2.2. The request forwarded from the Fastly edge POP to the Fastly shield POP therefore carries the header X-Forwared-For: 1.1.1.1, 2.2.2.2.

The shield POP will append the IP of the edge POP, provided that the backend is configured to use TLS for the connection from Fastly to origin. It then forwards the request to that backend, and the backend will receive a request carrying the header X-Forwarded-For: 1.1.1.1, 2.2.2.2, 3.3.3.3.

Modifying the default behavior

Any existing value present in the header when a request arrives at Fastly will be preserved. If you want to prevent this, set the value yourself when the request is first seen by Fastly:

sub vcl_recv { ... }
Fastly VCL
if (fastly.ff.visits_this_service == 0 && req.restarts == 0) {
set req.http.Fastly-Client-IP = client.ip;
set req.http.X-Forwarded-For = client.ip;
}

If your service uses shielding and you want to ensure that there is only a single entry in X-Forwarded-For when the request reaches your origin, you can override it on both the edge and shield:

sub vcl_recv { ... }
Fastly VCL
if (fastly.ff.visits_this_service == 0 && req.restarts == 0) {
set req.http.Fastly-Client-IP = client.ip;
}
set req.http.X-Forwarded-For = req.http.Fastly-Client-IP;

It is also possible to configure special rules for X-Forwarded-For using Request settings in the API or web interface.

TLS and shielding in detail

Modifications to X-Forwarded-For are considered (and made) before the request is presented to your service's VCL code (before vcl_recv is executed). In the case of the first Fastly POP to handle the request, that means we examine the connection from the client to Fastly to determine the TLS state, and if the connection is secure, the IP of the client is appended.

In the cast of shield POPs, the situation is more complex. Assignment of a backend happens in vcl_recv. If the backend assigned to req.backend is configured with shielding, and the current POP is not the shield POP, the backend is replaced automatically with a Fastly-generated director that causes the request to be forwarded to the shield POP. The director used for this purpose also depends on the TLS configuration of the backend - if the backend is configured to use TLS for the connection from Fastly to your origin, then the shield connection between the two Fastly POPs will also use TLS.

The X-Forwarded-For header modification is made on the shield POP if the connection between the edge POP and the shield POP is secure, and that connection is made securely if the backend assigned on the edge POP prior to shielding is setup to use TLS. The backend subsequently assigned by the shield POP has no effect and causes no further modification of the header, however it's uncommon for a service to assign a different backend at the shield than the one selected when the same service is executed by the edge POP.