Nginx反向代理后获得真实用户IP

因历史原因,手上有几个站一直在国外飘着,众所周知,网络颠簸,更别说还跨了个太平洋了,大多数回国线路晚高峰爆炸是经常的事,趁着促销正好入了台搬瓦工的CN2 GIA线路VPS,但是问题是CN2 GIA回国线路是很好,但是配置太低,配置好Nginx+PHP-FPM+MySQL之后再跑WordPress稍微有点流量就很吃力,于是这么久依赖我那几个站都是放在一台配置更高的后端服务器上,这台线路好的CN2 GIA VPS只是用来承担国内过去的前端流量,这就导致了一个问题,后端服务器拿不到真实的用户IP,这么久一来后端服务器拿到的都是前端服务器也就是搬瓦工的CN2 GIA线路的VPS的IP,而不是真实的用户IP,今天花了点时间终于解决了IP不一致的问题。

准备工作

首先,配置LNMP以及前端服务器反向代理前端用户请求到后端服务器是不用说了,这里直接贴一个前端服务器的配置文件:

server
{
        listen          80;
        server_name cloudbool.com www.cloudbool.com;
        location / {
                proxy_pass http://cloudbool.com/;
        }
                access_log /var/log/nginx/cloudbool.log;
        error_log /var/log/nginx/cloudbool-error.log;
}

这么几行就能配置一个Nginx反向代理服务器了,简单是很简单,但是这么做用户的访问IP只能在前端也就是配置反向代理的这台Nginx所在服务器才能获取到,后端服务器得到的只是这台前端服务器的IP,比如说这台前端服务器的IP是1.2.3.4,哪后端服务器的用户访问日志全部都是1.2.3.4,对于我们分析访问日志及配置网站就有点不便,我们需要的是在后端也能拿到用户的真实IP。

前端Nginx服务器配置

既然要在后端服务器拿到用户的真实IP,那就需要在前端Nginx服务器请求后端Nginx的时候一并将用户真实IP带上,这就需要用到Nginx的ngx_http_realip_module模块,一般来说,如果用的是Nginx官方源安装的Nginx,默认就是编译了这个模块,如果没有编译这个模块,可能需要手动编译,已经配置好的Nginx可以通过这个命令查看Nginx的编译情况:nginx -tngx_http_realip_module模块相关的文档地址:https://nginx.org/en/docs/http/ngx_http_realip_module.html
准备好ngx_http_realip_module模块之后,接着就是配置前端Nginx服务器了。
在前端Nginx服务器的需要代理的网站配置文件中加入或者更改如下代码:

location / {
     proxy_set_header Accept-Encoding "";
     proxy_pass https://cloudbool.com/archive/;
     proxy_set_header   Host    $host;
     proxy_redirect off;
     proxy_set_header X-Real-IP $remote_addr;
     proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}

也就是加上4、5、6、7行,将相应的真实IP传递到后端服务器。更改完之后,重启前端Nginx即可。

后端Nginx服务器配置

默认情况下,Nginx的日志格式是这样的:

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

如果是通过包管理器安装Nginx,默认的Nginx主配置文件是在/etc/nginx/nginx.conf
我们如果要接收前端Nginx传来的用户真实IP信息就需要更改下,将日志格式开头的$remote_addr更改成$http_x_forwarded_for,并在后面加上下面几行,相关部分配置如下:

log_format  main  '$http_x_forwarded_for - $remote_user [$time_local] "$request" '
                                    '$status $body_bytes_sent "$http_referer" '
                                    '"$http_user_agent" "$http_x_forwarded_for"';
set_real_ip_from 1.2.3.4;
real_ip_header X-Forwarded-For;
real_ip_recursive on;

上面的1.2.3.4需要替换成真实的前端Nginx服务器地址。除了单独的IPv4地址外,Nginx还支持CIDR格式及IPv6的地址格式。
配置完之后,需要重启Nginx,重启之后应该就能获取到真实的用户IP信息了,如图:
nginx-real-ip
实际中使用发现,ngx_http_realip_module模块可能和Nginx自带的allow/deny有冲突,如果后端服务器默认开启了allow/deny指令再使用ngx_http_realip_module模块,会导致前端访问返回403的问题,暂时没有找到合适的方法,搜索了一波发现有人遇到了这个问题但是几年了也没有人解决。
附带一个前端Nginx的完整配置文件:

server{
        listen 443 ssl http2;
        listen [::]:443 ssl http2;
        server_name  cloudbool.com www.cloudbool.com;
        ssl on;
        ssl_certificate /path/to/ssl/cloudbool-rsa.crt;
        ssl_certificate_key /path/to/ssl/cloudbool-rsa.key;
        ssl_session_cache        shared:SSL:10m;
        ssl_session_timeout      10m;
        ssl_session_tickets      on;
        resolver                 8.8.4.4 8.8.8.8  valid=300s;
        resolver_timeout         10s;
        ssl_session_cache builtin:1000 shared:SSL:10m;
        ssl_dhparam /path/to/ssl/dhparam.pem;
        ssl_prefer_server_ciphers on;
        ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3;
        ssl_ciphers [TLS13+AESGCM+AES128|TLS13+AESGCM+AES256|TLS13+CHACHA20]:[EECDH+ECDSA+AESGCM+AES128|EECDH+ECDSA+CHACHA20]:EECDH+ECDSA+AESGCM+AES256:EECDH+ECDSA+AES128+SHA:EECDH+ECDSA+AES256+SHA:[EECDH+aRSA+AESGCM+AES128|EECDH+aRSA+CHACHA20]:EECDH+aRSA+AESGCM+AES256:EECDH+aRSA+AES128+SHA:EECDH+aRSA+AES256+SHA;
        ssl_stapling on;
        ssl_stapling_verify on;
        location / {
           proxy_set_header Accept-Encoding "";
           proxy_pass https://cloudbool.com/archive/;
           proxy_set_header   Host    $host;
           proxy_redirect off;
           proxy_set_header X-Real-IP $remote_addr;
           proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        }
        access_log /var/log/nginx/cloudbool.log;
        error_log /var/log/nginx/cloudbool-error.log;
        error_page 404 /404.html;
        error_page 500 502 503 504 /50x.html;
        location = /50x.html {
                root /usr/share/nginx/html;
        }
}
server
{
        listen          80;
        server_name cloudbool.com www.cloudbool.com;
        location / {
                rewrite ^/(.*)$ https://cloudbool.com/archive/$1 permanent;
        }
}