博文头图
通过两个模块 使Nginx获取不同来源的访客IP

通过两个模块 使Nginx获取不同来源的访客IP

本文最后更新于587天前,其中的信息可能已经有所发展或是发生改变。

问题本身

Nginx是一个功能强大的Web服务器,它的强大体现在它的灵活性上。为了提高访客的访问速度,Nginx可以前置各种各样的CDN;为了实现复杂的功能,Nginx也可以前置TCP四层代理。然而,这些后置Nginx的做法有一个需要亟待解决的问题:如何让Nginx获取到访客的真实IP。

如果只是前者,一个CDN前置于Nginx,解决这个问题非常简单:只需要为Nginx安装ngx_http_realip_module模块,简单配置一下就可以。例如,Cloudflare会通过向后端的Nginx发送X-Forwarded-For请求头,其中就包含了访客IP。因此,只需在Nginx配置文件中加上一句real_ip_header X-Forwarded-For;就完美解决了。

如果只是后者,Nginx后置于TCP四层代理,则可以试试通过Proxy Protocol传递访客IP,当然这需要TCP四层代理软件的支持。同样需要ngx_http_realip_module模块,也是在Nginx配置文件中添加简单一句real_ip_header proxy_protocol;就可以。

然而,让我们考虑以下两个极端情况:

  1. 前面所说的两种情况同时出现,即既需要从CDN中获取访客IP,又需要在四层代理中获取。
  2. 同时使用两家CDN,两家CDN向后端Nginx发送的请求头不同,如CDN A发送X-Forwarded-For,CDN B发送X-Client-Real-Ip。下图描述的就是这种情况。

流量的传递

real_ip_header指令只能使用一次,配置文件中重复出现的指令将会报错:

Nginx报错

因此在这两种情况下,要想获取访客的IP地址,只能二选一,要么选择A放弃B,要么选择B放弃A。真的没有办法了吗?

可能的解决方法...?

其实思路还是很简单的,最简单的方法还是对请求进行判断,如果请求来源于A,就使用A的请求头中的访客IP;如果来源于B,就使用B的请求头中的访客IP。在Nginx中,这一点可以通过map指令来实现,下面就是一个判断访客IP是否来自于Cloudflare CDN的demo:

map $HTTP_CF_CONNECTING_IP $clientRealIp {
    "" $remote_addr;
    ~^(?P<firstAddr>[a-z0-9.:]+),?.*$ $firstAddr;
}

这条map指令创建了一个名为 $clientRealIp 的变量,通过 $HTTP_CF_CONNECTING_IP 变量的值进行映射。

  • "" $remote_addr;:如果 $HTTP_CF_CONNECTING_IP 变量为空(也就是访客来源并非是Cloudflare),则使用 $remote_addr 作为 $clientRealIp$remote_addr 是请求直接到达Nginx服务器的客户端IP地址。当然,也可以换成其他CDN获取到的访客IP地址。
  • ~^(?P<firstAddr>[a-z0-9.:]+),?.\*$ $firstAddr;:如果 $HTTP_CF_CONNECTING_IP 变量有值,则使用正则表达式从中提取第一个IP地址。$HTTP_CF_CONNECTING_IP 中包含了Cloudflare传递的客户端真实IP地址。

现在,$clientRealIp中的值就是访客的真实IP了,如果将它放到real_ip_header指令中,似乎问题就能...解决啦?


然而并不可以,你会发现日志中获取到的还是CDN的回源IP,并没有获取到访客的IP。这是因为real_ip_header指令中后面跟的参数是一个Header而不是一个String,我们获取到的$clientRealIp是一个访客IP的字符串,real_ip_header指令并不认识它。

要想让它认识,需要将访客IP字符串包装成一个Header,这就需要用到另外一个模块,nginx-module-headers-more。下面就利用这个模块正确的从多个来源获取访客的IP。

问题的解决

Step1. 重新编译Nginx

第一步需要重新编译Nginx,将nginx-module-headers-more作为参数编译到Nginx中;当然,如果你的Nginx编译参数中原本就没有ngx_http_realip_module模块,编译的时候不要忘了一并添加进来。

nginx-module-headers-more模块的相关文件需要在这里下载:https://github.com/openresty/headers-more-nginx-module

wget https://github.com/openresty/headers-more-nginx-module/archive/refs/tags/v0.37.zip
unzip v0.37.zip
mv headers-more-nginx-module-0.37 headers-more-nginx-module
cd nginx-1.25.5
./configure [other-args] --with-http_realip_module --add-module=../headers-more-nginx-module
make
make install

以上命令仅供参考,这样就完成了编译安装。当然,你也可以采用使用动态链接的方法引入模块,作用相同,在此不做赘述。

Step2. 配置Nginx

这里用从Cloudflare CDN和TCP四层代理两处来源获得访客IP做例子来进行演示,其他情况可以参照着这个来。

#nginx.conf
...
  map $HTTP_CF_CONNECTING_IP $clientRealIp {
    "" $proxy_protocol_addr; //Proxy Protocol获取到的四层TCP代理访客IP
    ~^(?P<firstAddr>[a-z0-9.:]+),?.*$ $firstAddr;
  }
...

#vhost
...
  more_set_input_headers "X-IP: $clientRealIp";
  real_ip_header X-IP;
...
  • more_set_input_headers:利用nginx-module-headers-more模块,自定义一个名为X-IP的请求头,请求头的内容就是我们获取到的字符串格式的访客IP。
  • real_ip_header:使我们自定义的X-IP请求头生效。

实际上操作起来非常简单,原理同样也通俗易懂。重启Nginx,你的Nginx应该能够从两个不同的来源获取访客IP了。当然,如果要从多个来源获取的话,原理也相近,只是map指令要复杂一些。

之前本站采取了偷懒的做法,直接使用了参考资料链接一中的方法,将访客IP直接写入日志,因为那时候本站只需要将访客IP写入日志进行分析。但是要在Nginx中进行一些复杂的操作就会立即露马脚,比如根据访客的IP进行封禁,Nginx本身拿到的还是CDN的回源IP,直接写规则会是失效;而本站最近在Nginx侧启用了WAF,问题就更加严重了。经过上面的摸索解决了我的问题,希望也能帮到你。

PS. 如果正经做站或者有业务,强烈建议屏蔽腾讯云、阿里云和Ucloud的IP,没有真人流量,全是各种爬虫和攻击流量。本站已经全数对它们进行屏蔽。

参考资料

使用CF CDN服务后,nginx日志文件记录中的真实ip问题 | 未末

How to use multiple real IP headers with nginx - GetPageSpeed

本文永久链接:https://blog.xmgspace.me/archives/get-client-ip-from-different-sources-on-nginx.html
本文文章标题:通过两个模块 使Nginx获取不同来源的访客IP
如无特殊说明,只要您标明转载自Xiaomage's Blog,就可转载本文。若无法复制请联系站长获取文章副本。
授权协议:署名-非商业性使用-相同方式共享 4.0(CC BY-NC-SA 4.0)

评论

  1. 匿名
    Android Firefox
    已编辑
    6 月前
    2025-12-13 16:51:22

    看来屏蔽阿里云是对的 https://www.v2ex.com/t/1178158
    :no-location:

    • 博主
      匿名
      Windows Chrome浙江
      6 月前
      2025-12-17 15:02:59

      是这样的hhhh
      阿里 腾讯 Ucloud DigitalOcean Choopa这类相对低门槛的云服务都最好屏蔽,或者上CF的质询。

  2. 匿名
    Android Firefox
    7 月前
    2025-12-07 10:49:00

    歪个楼,哪天我用腾讯云服务器代理访问合法的境外网站,例如贵站
    不过都用 cf cdn,不如只允许 cf ip 访问
    :no-location:

    • 匿名
      匿名
      Android Firefox
      7 月前
      2025-12-07 10:51:37

      这里的代理不是说绕过审查,只是避免暴露家宽 ip
      :no-location:

      • 博主
        匿名
        Windows Chrome山东
        7 月前
        2025-12-07 13:13:37

        可以的,服务器端记录的是你的腾讯云服务器的IP,可以避免你家宽IP的暴露。

        • 匿名
          Xiaomage
          Android Firefox
          已编辑
          7 月前
          2025-12-07 15:54:08

          你好像没有理解我的意思,此时我用腾讯云 ip 访问贵站不就被屏蔽了
          :no-location:

    • 博主
      匿名
      Windows Chrome山东
      7 月前
      2025-12-07 13:17:48

      当然是可以的,如果只用CF的IP的话,只允许CF访问源服务器,是防止源站IP暴露的一种很好的方法
      CF会把访客的IP传递给源站的

  3. Android Chrome浙江
    1 年前
    2025-3-31 23:52:49

    请问一下,我用的是内网穿透FRP(支持proxy protocol),web服务端用的是openresty(1Panel用的),但是我不知道如何让openresty能够处理frpc传过来的proxy protocol请求头,请问也适用于这篇文章吗?

    • 博主
      dee
      Windows Chrome山东
      1 年前
      2025-4-05 17:48:11

      抱歉,你的评论被系统吞了,这才看到(
      我觉得你的需求直接在openresty配置文件的listen这一行,添加proxy_protocol就可以了吧,这样就直接可以从frp添加的proxy protocol请求头里面拿到客户端的IP了。
      也可能是我没明白你的意思,你的问题似乎没有涉及到处理多个来源访客IP的问题,frp的文档应该对你有帮助:https://gofrp.org/zh-cn/docs/features/common/realip/

  4. Windows Edge
    2 年前
    2024-8-24 12:05:03

    之前我尝试是用Lua去解决融合CDN传递客户端真实IP不同的情况。看了博主的文章又学到了新的方法~ :no-location:

发送评论 编辑评论


				
若您不希望公开IP属地,请点击此处,在评论末尾添加:no-location:标记。IP属地仅供参考。隐私政策
上一篇
下一篇