西西软件园多重安全检测下载网站、值得信赖的软件下载站!
软件
软件
文章
搜索

首页 → 业内动态 → 业内资讯 → Nginx日志全是CDN节点IP:加了XFF头还拿不到访客真实地址

Nginx日志全是CDN节点IP:加了XFF头还拿不到访客真实地址

相关软件相关文章发表评论 来源:本站整理时间:2026/11/3 2:58:34字体大小:A-A+

作者:xiix点击:1次评论:0次标签: 真实IP X-Forwarded-For real_ip_header Nginx反代 CDN回源

Nginxv1.27.1 官方稳定版
  • 类型:服务器区大小:1.7M语言:英文 评分:5.0
  • 标签:
立即下载

去年有位做在线教育的客户找到我,说他的站点上了 CDN 之后,访问日志里全是腾讯云、阿里云那些边缘节点的 IP,一个真实访客都看不到。风控要靠 IP 拦刷单,运营要统计独立访客数,两个需求都卡死在日志上。更麻烦的是,他一开始自己就加了 X-Forwarded-For,日志里确实多出一串地址,兴奋了半小时,然后发现从第二跳开始,那串值还是 CDN 节点,根本不是访客。

这个坑我见过不止一次。它的根源不是 Nginx 配错,而是「到底该信谁」这件事没想清楚:你的服务器前面到底有几层代理,每一层加的头是什么,哪些层可信、哪些层可以随时伪造。今天这篇就把这条链路拆开讲,顺手把 real_ip_header 这条最省事的配置用明白。

如果你也在做站、也在挂 CDN,或者准备给服务器加一层反代,这台可以先放进备选:RackNerd(全球VPS,特价常推)、狗云DOGYUN(高性价比云服务器,多机房可选)。机器选好只是第一步,后面这几层代理的关系更值得花时间捋一遍。

CDN边缘节点与源站之间的回源链路

一、先看清楚你的请求到底经过了几层

在改任何配置之前,先打开浏览器的开发者工具,看一眼这个请求的完整链路。最常见的三种情况是:

第一种,客户端直连你的 Nginx,没有 CDN 也没有前置负载。这时候 $remote_addr 就是访客真实 IP,直接用就行,不用折腾任何头。

第二种,前面挂了一层 CDN。CDN 接到请求后,会用它的边缘节点 IP 回来访问你的源站,同时在请求头里塞上真实访客 IP 不同厂商用的头不一样,常见的是 X-Forwarded-For、X-Real-IP、还有 Cloudflare 那种 CF-Connecting-IP。

第三种,最麻烦的:CDN 后面还架着自己的反代,比如 CDN → 你的负载均衡 → Nginx → PHP。这时候到达 Nginx 的请求头里已经叠了两三个同名头,甚至还有伪造的成分。

请把这一段画出来。记住一句话:越靠近访客的那一跳,地址越可信;越靠近你的那一跳,越可能是别人塞进来的。

多级反向代理链路的层级结构

二、X-Forwarded-For 是一串,不是一个

这是最容易误解的地方。XFF 的格式是逗号分隔的一串地址,越靠左越接近最初的客户端,越靠右越靠近你的服务器。

举个三层链路的例子。访客真实 IP 是 1.1.1.1,他访问了 CDN,CDN 边缘节点 IP 是 2.2.2.2,CDN 回源时把 XFF 写成 1.1.1.1;然后 CDN 又经过了一层你自己公司的反代(IP 是 3.3.3.3),这层反代按照惯例会追加自己看到的来源 IP,于是头变成 1.1.1.1, 2.2.2.2;最后这层反代把请求交给 Nginx,Nginx 看到的 XFF 就是 1.1.1.1, 2.2.2.2。

所以在 Nginx 里,$http_x_forwarded_for 拿到的可能是 1.1.1.1, 2.2.2.2 这样一串,而不是一个干净的地址。很多人直接把它塞进日志,看起来「有东西了」,但真正要用的是第一个值。

这里还有个更要命的安全点:XFF 是可以伪造的。只要攻击者手动发一个 X-Forwarded-For: 8.8.8.8 的请求,他在 XFF 里的最左边就会插一个假地址。如果你的程序无脑取「最左边第一个」,等于把 IP 判定的权力交给了客户端。所以取哪一端,取决于你信任链上有几层。

运维人员在终端查看Nginx访问日志

三、real_ip_header + set_real_ip_from 这套最省事

与其在日志格式里写一堆 if 判断去切字符串,不如让 Nginx 官方模块来做这件事。核心是两条指令:

set_real_ip_from 你的CDN网段; 声明「这个网段发来的 XFF 我信」;
real_ip_header X-Forwarded-For; 声明「可信来源用这个头里的真实地址」。

配完之后,$remote_addr 里存的就是真实访客 IP 了,日志格式、统计脚本、后台代码一行都不用改,这是我最推荐的做法,因为它是「一次性修好,全站生效」的。

但有个关键细节千万别漏:set_real_ip_from 必须写你 CDN 的出站回源网段,也就是 CDN 节点访问你源站时用的 IP 段,而不是写访客网段,也不是写 0.0.0.0/0。写全网段等于把所有能直连你服务器的陌生人都当成可信来源,他们随便编一个 XFF 头就能把日志刷成假 IP,比不开还糟。

如果你的链路不止一层(比如 CDN → 自建反代 → Nginx),那么 set_real_ip_from 要写两段:CDN 的回源网段,以及你自己那层反代的网段。然后 real_ip_header 的取值行为是:Nginx 只信任「来自 set_real_ip_from 列出的地址」的请求头,并且会自动取 XFF 链中最右侧的非可信地址作为真实 IP。这正好符合直觉链条里从我往外数,第一个不是自己人的地址,就是它前面的那个可信代理告诉我的真实来源。

如果 CDN 用的是 X-Real-IP 或者 CF-Connecting-IP,把 real_ip_header 改成对应的名字就行。建议先用 curl -H "X-Forwarded-For: 8.8.8.8" https://你的域名 手工造一个假头,看日志里记录的是 8.8.8.8 还是真实地址这一步能立刻验证你的白名单到底有没有生效。

数据包在多层代理之间逐层传递

四、如果懒得配 Nginx,也可以先在应用层取

有些场景下改 Nginx 需要重载服务、会影响线上业务,运维那边一时排不上期。那就先在应用里取,最常见的是 PHP 里读 $_SERVER['HTTP_X_FORWARDED_FOR'],然后按逗号切分,取其中一段。

但取哪一段,还是回到上面那个信任链的判断:如果你确定只有 CDN 一层,那就取最左边;如果是两层,就取从右往左数第二个。

再补充一个容易被忽略的点:Nginx 默认的 log_format 里 $remote_addr 写的是连接对端地址。很多人在 http 段全局挂了 set_real_ip_from,但某个 server 块里又自己写了一份 log_format,里面用了 $http_x_forwarded_for 之外的老写法,结果日志还是老样子。改的时候记得 nginx -t 测一下,再 nginx -s reload,别直接重启。

五、几个容易翻车的细节

其一,CDN 的 IP 段会变。各家 CDN 的回源网段列表都是会更新的,隔几个月就该重新核对一遍官方文档。我们有个客户就是没核对,某天 CDN 调整了回源节点,他们 Nginx 里的白名单没跟上,真实 IP 又开始全变成 CDN 节点了。

其二,本地开发环境会「假装」一切正常。你在本地 curl 本机,XFF 头是你自己伪造的,看起来配置完美,一上线就全乱。测试的时候请务必从外网实际访问一次。

其三,别把真实 IP 逻辑只写在日志里。如果风控、限流、白名单这些安全逻辑读的还是 getRemoteAddr(),那日志里显示对了 IP,业务逻辑却还在按 CDN 节点 IP 判断,等于白配。要么统一用 real_ip 模块后的 remote_addr,要么在应用层显式取。

其四,日志里的 IP 要考虑被伪造的后果。如果你的站点允许游客自定义 Referer 或者某个会回显到日志的字段,注意别给刷日志留下太便宜的口子。

六、验证的三个动作

改完之后,做这三步基本就能确认生效了:

第一步,看一条真实日志,确认 remote_addr 字段是访客公网 IP,而不是 CDN 节点 IP。

第二步,用 curl -H "X-Forwarded-For: 8.8.8.8" https://你的域名 造一个假头,如果日志里出现的是 8.8.8.8,说明白名单形同虚设,需要立刻修;如果出现的是你自己本地的出口 IP,说明伪造被挡住了。

第三步,从不同网络环境各访问一次(手机流量、公司宽带、家里 WiFi),看看取到的地址是不是分别对应这三个网络的出口 IP。三个都对,才算真的通了。

回头看这件事,X-Forwarded-For 本身不难,难的是没人告诉你该信链上的哪一环。很多人绕了大半年,其实只需要先画一张链路图,数清楚有几层,再决定取 XFF 的第几个值。这张图不但这次能用,下次换 CDN、加一层负载均衡,答案也直接从图上读。

    相关评论

    阅读本文后您有什么感想? 已有人给出评价!

    • 8 喜欢喜欢
    • 3 顶顶
    • 1 难过难过
    • 5 囧囧
    • 3 围观围观
    • 2 无聊无聊

    发表评论

    昵称:
    表情: 高兴 可 汗 我不要 害羞 好 下下下 送花 屎 亲亲