Typically your public web server with consist of two principle pieces of software, the web server such as Nginx (Apache is more commonly used) and some server side scripting technology such as php (in my case php-fpm).
However many people are now either installing cache engines (such as Varnish) or load balancers infront of their web server to increase performance and possibly availability. Which can play havoc for server side scripts that use the client information.
Your HTTP requests are now handled by the cache (Varnish) so PHP variables such as $_SERVER[‘REMOTE_ADDR’] will no longer hold the client IP, however they will have your varnish cache IP, 127.0.0.1 if it’s on the same physical server.
This poses a problem because many scripts now use the client IP address, this could be for Spam protection, session tracking/security, or just statistics logging.
Some may say you should use another variable in your script such as $_SERVER[‘X-Forwarded-For’], this way your script can know the client IP and also the proxy IP, while the theory might have some benifits most people don’t write their own code, and chances are it’s configured to use REMOTE_ADDR.
So the fix is to have Varnish assign the real user’s IP address to a variable called “X-Forwarded-For”, this is done as below:
#Backup the configuration cp /etc/varnish.default.vcl /etc/varnish.default.vcl.backup #Open your varnish configuration nano -w /etc/varnish.default.vcl #Add the following lines to sub vcl_recv sub vcl_recv { remove req.http.X-Forwarded-For; set req.http.X-Forwarded-For = req.http.rlnclientipaddr; } #Save and exit, then restart the varnish service service varnish restart
So your cache engine is now passing the IP back to Nginx, now we just need configure Nginx to capture this variable and replace REMOTE_ADDR with it. To do this you need a Nginx module installed called http_realip_module, so run the following command and look out for “–with-http_realip_module”:
nginx -V nginx version: nginx/0.7.67 TLS SNI support enabled configure arguments: --conf-path=/etc/nginx/nginx.conf --error-log-path=/var/log/nginx/error.log --http-client-body-temp-path=/var/lib/nginx/body --http-fastcgi-temp-path=/var/lib/nginx/fastcgi --http-log-path=/var/log/nginx/access.log --http-proxy-temp-path=/var/lib/nginx/proxy --lock-path=/var/lock/nginx.lock --pid-path=/var/run/nginx.pid --with-debug --with-http_dav_module --with-http_flv_module --with-http_geoip_module --with-http_gzip_static_module --with-http_realip_module --with-http_stub_status_module --with-http_ssl_module --with-http_sub_module --with-ipv6 --with-mail --with-mail_ssl_module --add-module=/tmp/buildd/nginx-0.7.67/modules/nginx-upstream-fair
As you can see I have it installed already, as you hopefully will do, now I just need to use the following configuration to perform the variable replacement.
#Create a backup of the configuration cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.backup #Edit the configuration file nano -w /etc/nginx/nginx.conf #Add the following lines to your http{} statement set_real_ip_from 127.0.0.1; real_ip_header X-Forwarded-For; #Save, close and restart the nginx service service nginx restart
Ok, just to explan what that configuration did, “set_real_ip_from” is going to be the IP address of your cache/load balancer basically the IP your currently seeing all of the proxied requests come from, in my case this was 127.0.0.1 as the cache and nginx are on the same server.
And “real_ip_header” defines what variable actually has the correct information for “REMOTE_ADDR” as you will see this matches what we setup Varnish to pass it back as.
You should be able to test if this works using the below test script.
echo $_SERVER["REMOTE_ADDR"] ; echo " ".time()." "; echo $_SERVER["HTTP_X_FORWARDED_FOR"] ;
Echoing the time just makes sure your not getting a varnish cache hit.
Hope that helps, please leave any questions you still have below…
5 comments On Nginx PHP REMOTE_ADDR with Proxy (Varnish Cache)
Hello,
Thanks for this tip !
Varnish & nginx rock 😉
Hey Tom,
Thank you very much for this! I now have proper IP addresses in my access logs (served by NGINX behind Varnish).
Strangely, Bing Bot still shows as 127.0.0.1 on every visit, but apart from that, your fix works great! Would you have any thoughts about this, at all?
Here’s an example of what I’m seeing…
127.0.0.1 – – [11/Dec/2012:20:40:25 +0000] “GET /tag/tagname HTTP/1.1” 200 5733 “-” “Mozilla/5.0 (compatible; bingbot/2.0; +http://www.bing.com/bingbot.htm)”
Thanks again,
Mark.
In your Nginx access log do the other visitors show with the correct source ip?
Thanks, works perfectly, perfect tutorial. +1
Thanks for that as it was quite helpful.
For anyone that is using docker with seperate containers for nGinx and Varnish and the AWS ELB.
Varnish
——-
sub vcl_recv {
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;
}
}
set req.http.x-server-address = std.ip( regsub(req.http.X-Forwarded-For, “^([^,]+),?.*$”, “\1”) , “0.0.0.0”);
nginx.conf http { } block
set_real_ip_from 0.0.0.0/0;
real_ip_header x-server-address;
You should be able to see the variable $_SERVER in PHP:
[HTTP_X_SERVER_ADDRESS] =