I have Varnish running behind a reverse proxy (running on localhost, for SSL offloading). The proxy sets the X-Forwarded-For header or adds itself to it if the header already exists.

When I do ACL checks of course I want to check against the original client's IP, not the IP of my proxy, so I can't use the client.ip field. With the std vmod I can do the following:

vcl 4.0;
import std;
sub vcl_recv {
    if (std.ip(regsub(req.http.X-Forwarded-For, ",$", ""), "") ~ my_acl) {
        ...do stuff...

In other words, I trim the proxy's IP ( from the header before running it through std.ip and comparing it to my ACL. This works fine, except...

This fails when the X-Forwarded-For header is already set before it reaches my proxy. In that case the XFF header contains three or more IP addresses. Trimming the last one still leaves more than one and std.ip chokes on this, delaying the request by several seconds and of course failing to check the ACL.

I need to make sure the XFF header contains only one IP (IPv4 or IPv6) after I've trimmed the proxy's off. This should be the client's IP.

For example:

X-Forwarded-For:,, 2001:a031:100a:dead:beef:1234:1234:1234,

should become

X-Forwarded-For: 2001:a031:100a:dead:beef:1234:1234:1234,

Since I can't trust any XFF headers that come in from outside, I'd like to discard anything but the client-ip that my proxy saw. My proxy doesn't support modifying the XFF header so I'll need to do it in Varnish.

The first point in Varnish's flow when I can interact with headers is in vcl_recv() and at that point Varnish has already added the client.ip to the end of the list.

I was hoping to use a regular expression to capture the last two items (IPv4 or IPv6) into a numbered capture group ($1) and simply replace the header with the capture group. Like this:

vcl 4.0;
import std;
sub vcl_recv {
    set req.http.X-Forwarded-For = regsub(req.http.X-Forwarded-For, "([a-f0-9:.]+, [a-f0-9:.]+)$", "\1");

The third argument (the string that replaces the characters matched by the regex) doesn't work. The resulting header is exactly the same as before, even though the regex captures only the last two IP adresses.

How can I throw away anything but the last two IP addresses from the XFF header?


Your regex seems fine. The only thing I see is that you request to replace the group by itself.

It should work if you add ^(.*) at the beginning of your regex and replace the second argument by \2

set req.http.X-Forwarded-For = regsub(req.http.X-Forwarded-For, "^(.*)([a-f0-9:.]+, [a-f0-9:.]+)$", "\2");


That being said I don't think it's a good idea to change the XFF header. It should be more standard to add a header rather than changing deleting information that has been transmitted by intermediate proxies.

  • Yes, adding the part in front works. I ended up with "^(?:[a-f0-9:.]+, )*([a-f0-9:.]+, [a-f0-9:.]+)$". I made the first group non-capturing so I could keep \1. – Martijn Heemels Feb 28 '17 at 16:36
  • I could create a different header, but all apps and logging tools are already set up to use XFF. Since we can't really trust any values already in the header when it enters our network I don't think we lose anything of value in modifying the header, right? It would be better to discard the header at the first proxy, but it doesn't have that feature yet. – Martijn Heemels Feb 28 '17 at 16:42

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.