3

Been scratching my head for a few hours now and wanted to see if anyone can help.

1) I have a load balancer with 6 servers in the back end.

2) The back end servers are Nginx and to get the real IP addresses of the visitors, all I have to do is the following in each Nginx install and I am able to get the real client IP address of each visitor.

 set_real_ip_from 192.168.255.0/24; <-- to handle the load balancer IP
 real_ip_header X-Forwarded-For;

3) Now I have installed Varnish in front of each Nginx running on 127.0.0.1 doing caching and for some reason now Nginx doesn't see the real client Ip addresses anymore coming from LoadBalancer --> Varnish --> Nginx

It's printing the following:

IP address: 192.168.255.9 <-- this should be the real client IP address and not the 192.168 (assuming the load balancer IP address is being printed)

More detailed host address: 192.168.255.9

Many thank if you can help.

Dave

UPDATE:

Without Varnish in the equation, I have the following LB --> NGINX and within NGINX the following existis

set_real_ip_from  192.168.255.0/24;
real_ip_header X-Forwarded-For;

When NGINX logs the remote_addr, the first entry below prints the real client IP address

log_format main '$remote_addr - $remote_user [$time_local] "$request" '
              '$status $body_bytes_sent "$http_referer" '
              '"$http_user_agent" "$http_x_forwarded_for"';


213.205.234.x - - [05/Sep/2012:09:42:08 -0700] "GET /2011/10/28/chicken-and-apples-in- honey-mustard-sauce/ HTTP/1.1" 200 18283 "-" "Mozilla/5.0 (Linux; U; Android 4.0.4; en-gb; GT-I9100 Build/IMM76D) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30" "213.205.234.x"

With Varnish in the equation LB --> Varnish --> NGINX

And within NGINX I switched the set_real_ip_from to point to 127.0.0.1

set_real_ip_from  127.0.0.1;
real_ip_header X-Forwarded-For;

$remote_addr in NGINX doesn't print the real client IP address:

192.168.255.9 - - [05/Sep/2012:09:46:41 -0700] "GET /2012/09/03/stuffed-baked-potatoes-deconstructed/ HTTP/1.1" 200 18159 "-" "Mozilla/5.0 (Linux; U; Android 2.2.1; en-us; ADR6400L Build/FRG83D) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1" "69.255.125.x, 192.168.255.9"

As you can see from above the $remote_addr being printed is the Load Balancer's IP address: 192.168.255.9 instead of the client's remote_addr. Although the "$http_x_forwarded_for"' is printing the correct address I guess: "69.255.125.x, 192.168.255.9". My goal is to have the $remote_addr hold the correct IP address instead

Thanks Dave

UPDATE:

Here's my default.vcl from Varnish, commented out the part mentioned by Shane, here's the current output from the access log from NGINX

127.0.0.1 - - [05/Sep/2012:11:16:43 -0700] "GET /wp-content/plugins/wp-pagenavi/pagenavi-css.css?ver=2.70 HTTP/1.1" 304 0 "http://mobilefoodblog.com/2011/10/28/chicken-and-apples-in-honey-mustard-sauce/" "Mozilla/5.0 (Linux; U; Android 2.3.5; en-us; SCH-I405 Build/GINGERBREAD) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1" "67.168.209.192, 192.168.255.9"


    # This is a basic VCL configuration file for varnish.  See the vcl(7)
    # man page for details on VCL syntax and semantics.
    # 
    # Default backend definition.  Set this to point to your content
    # server.
    # 

    backend default {
         .host = "localhost";
         .port = "8080";
    }

    sub detect_device {
      # Define the desktop device
      set req.http.X-Device = "desktop";

      if (req.http.User-Agent ~ "iP(hone|od)" || req.http.User-Agent ~ "Android" || req.http.User-Agent ~ "iPad") {
        # Define smartphones and tablets
        set req.http.X-Device = "smart";
      }

      elseif (req.http.User-Agent ~ "SymbianOS" || req.http.User-Agent ~ "^BlackBerry" || req.http.User-Agent ~ "^SonyEricsson" || req.http.User-Agent ~ "^Nokia" || req.http.User-Agent ~ "^SAMSUNG" || req.http.User-Agent ~ "^LG") {
        # Define every other mobile device
        set req.http.X-Device = "other";
      }
    }

    acl purge {
            "localhost";
    }

    sub vcl_recv {
       call detect_device;

         # if (req.restarts == 0) {
         #  if (req.http.x-forwarded-for) {
         #      set req.http.X-Forwarded-For =
         #          req.http.X-Forwarded-For ", " client.ip;
         #  } else {
         #      set req.http.X-Forwarded-For = client.ip;
         #  }
         # }

       if (req.request == "PURGE") {
            if (!client.ip ~ purge) {
                 error 405 "Not allowed.";
            }
            return(lookup);
       }

    if (req.url ~ "^/$") {
          unset req.http.cookie;
        }
    }

    sub vcl_hit {
            if (req.request == "PURGE") {
                    set obj.ttl = 0s;
                    error 200 "Purged.";
            }
    }

    sub vcl_miss {
        if (req.request == "PURGE") {
                    error 404 "Not in cache.";
        }

        if (!(req.url ~ "wp-(login|admin)")) {
                            unset req.http.cookie;
        }

        if (req.url ~ "^/[^?]+.(jpeg|jpg|png|gif|ico|js|css|txt|gz|zip|lzma|bz2|tgz|tbz|html|htm)(\?.|)$") {
               unset req.http.cookie;
               set req.url = regsub(req.url, "\?.$", "");
        }

        if (req.url ~ "^/$") {
               unset req.http.cookie;
        }
    }

    sub vcl_fetch {
            if (req.url ~ "^/$") {
               unset beresp.http.set-cookie;
            }

        if (!(req.url ~ "wp-(login|admin)")) {
               unset beresp.http.set-cookie;
        }
    }

    sub vcl_hash {
         set req.hash += req.url;
         if (req.http.host) {
             set req.hash += req.http.host;
         } else {
             set req.hash += server.ip;
         }

         # And then add the device to the hash (if its a mobile device)
         if (req.http.X-Device ~ "smart" || req.http.X-Device ~ "other") {
           set req.hash += req.http.X-Device; 
         }

         return (hash);
    }

3 Answers 3

4

Since Varnish is running on each server that's running nginx, the source of the connection from the perspective of nginx is 127.0.0.1, not the load balancer anymore.

set_real_ip_from 192.168.255.0/24;

That's the problem; nginx won't 'trust' the X-Forwarded-For header when the connection originates from 127.0.0.1 (the Varnish process); all it trusts is the entire 192.168.255.0/24 network. Add an authorization to trust the header when Varnish sends it.

set_real_ip_from 127.0.0.1;

Edit:

nginx behaves badly when parsing the X-Forwarded-For header for the "real" client IP; it looks for the last entry in the header, which is never the real client IP when there's more than one entry. See this question for more info on this problem.

I'd recommend getting Varnish to stop adding its own X-Forwarded-For header. You'll want to strip this part of the vcl_recv function:

if (req.restarts == 0) {
    if (req.http.x-forwarded-for) {
        set req.http.X-Forwarded-For =
            req.http.X-Forwarded-For + ", " + client.ip;
    } else {
        set req.http.X-Forwarded-For = client.ip;
    }
}

Provide your current vcl config if you need assistance what needs to change for you, as this may be explicitly configured or being appended by the defaults (or both).

Edit 2:

Swap this in for the vcl_recv function in your Varnish config; it combines the customizations you've configured with the default behavior while removing the X-Forwarded-For trickery that's present by default.

sub vcl_recv {
    call detect_device;
    if (req.request == "PURGE") {
        if (!client.ip ~ purge) {
            error 405 "Not allowed.";
        }
        return(lookup);
    }
    if (req.url ~ "^/$") {
        unset req.http.cookie;
    }
    # Default logic follows; it's normally appended.
    # It'll still be appended, but having the return(lookup)
    # prevents its use. X-Forward-For header behavior removed.
    if (req.request != "GET" &&
      req.request != "HEAD" &&
      req.request != "PUT" &&
      req.request != "POST" &&
      req.request != "TRACE" &&
      req.request != "OPTIONS" &&
      req.request != "DELETE") {
        /* Non-RFC2616 or CONNECT which is weird. */
        return (pipe);
    }
    if (req.request != "GET" && req.request != "HEAD") {
        /* We only deal with GET and HEAD by default */
        return (pass);
    }
    if (req.http.Authorization || req.http.Cookie) {
        /* Not cacheable by default */
        return (pass);
    }
    return (lookup);
}
5
  • Hi Shane, thank you very much for your response. I have updated my original posting by setting the set_real_ip_from: set_real_ip_from 127.0.0.1; Need a bit more help to get this fully resolved. Thanks Dave
    – ddavtian
    Sep 5, 2012 at 16:51
  • @ddavtian Edited my answer with the cause of that and a solution. Sep 5, 2012 at 17:04
  • I added my default.vcl, the second you mentioned is not part of my vcl_recv. Thanks Dave
    – ddavtian
    Sep 5, 2012 at 17:31
  • @ddavtian See new edit - that combines your logic with the default while stripping the X-Forwarded-For behavior. Sep 5, 2012 at 18:55
  • Thanks so much Shane, the new updated vcl_recv you provided, it did the trick, the client address is coming in correctly. As a side note, I also updated to nginx version: nginx/1.2.3 and also within the site config in nginx I added: real_ip_recursive on;
    – ddavtian
    Sep 5, 2012 at 20:08
2

It sounds like your load balancer is not passing the client IP address in any header. Since you didn't say what you were using for the load balancer, it's impossible to give a specific solution, but in general you want to configure the load balancer to place the client IP address in one of the headers (such as X-Forwarded-For). You could also designate a custom header for this if you wanted.

If your load balancer is setting X-Forwarded-For, then I'd configure your Varnish servers to set a header other than X-Forwarded-For, so that you have full visibility of both the client's IP address and which server handled the request.

4
  • Thanks for your response Michael. The load balancer is a hosted solution and Linode and I can confirm that it is in fact passing the X-Forwarded-For header correctly. Before I introduced Varnish into the equation it worked like a champ when I only had the LB --> NGINX. In NGINX I had the following to grab the real IP address: set_real_ip_from 192.168.255.0/24; real_ip_header X-Forwarded-For;
    – ddavtian
    Sep 4, 2012 at 23:09
  • 1
    In that case I'd configure Varnish to send the client's IP address in a custom header, other than X-Forwarded-For, so you can tell both what the client IP address was and where the load balancer sent it. Sep 4, 2012 at 23:09
  • Any chance you would have a working example of this, been banging against the wall for a day now. Thank you!
    – ddavtian
    Sep 4, 2012 at 23:11
  • Somebody else might, but varnish is not my cup of tea (I use nginx instead). Sep 4, 2012 at 23:13
0

Assuming your load balancer uses the X-Forwarded-For header for both HTTP and HTTPS, then ...

... in Varnish you should have this

sub vcl_recv { 
...
  set req.http.X-Forwarded-For = req.http.X-Forwarded-For;

... in Nginx you should have this

server {
...
  set_real_ip_from 192.168.255.0/24;
  real_ip_header X-Forwarded-For;

I'll add that you should only really be running 1 Varnish instance, it defeats the purpose to have multiple caches (as the load balancer may pass a request to an un-primed Varnish instance). Your hit rates will be worse not to mention it just being a waste of resources.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .