发布时间:2026-09-22 05:00 更新时间:2026-09-22 05:00 阅读量:0
站点接上 CDN 之后,很多人第一件事是打开宝塔面板的网站日志,结果发现访问记录里的 IP 全变成了几个固定的地址,而且来来回回就那么几个。这不是日志坏了,而是 Nginx 默认只认 TCP 连接的对端地址——开了 CDN 以后,对端就是 CDN 的回源节点,真实访客的 IP 被藏在 HTTP 请求头里带过来了。这篇文章把日志格式、转发头字段和 real_ip 模块的配置串起来讲一遍,配好之后日志能重新看到访客真实地址,限流、封禁、统计这些依赖 IP 的功能也才有意义。
访客访问你的域名,DNS 解析到 CDN 节点,CDN 节点再去你的源站拉内容。站在源站 Nginx 的角度,这条 TCP 连接是 CDN 节点发起的,所以 $remote_addr 取到的是回源节点 IP,跟我们想要的访客地址没关系。
CDN 厂商在回源时会额外加几个请求头,常见的有 X-Forwarded-For、X-Real-IP,部分厂商用自己的私有头(比如 Cloudflare 的 CF-Connecting-IP)。其中 X-Forwarded-For 是一个逗号分隔的列表,格式大致是「客户端IP, 代理1IP, 代理2IP」,每经过一层代理就往后追加一段。X-Real-IP 通常只放一个地址,多数实现里就是最后一段可信代理记录的那个客户端地址。
需要提醒的是,这些头是普通 HTTP 头,任何人都可以伪造。所以直接用 $http_x_forwarded_for 写进日志是不安全的,必须配合 real_ip 模块,让它只信任来自 CDN 回源段的连接,其它来源的转发头一律忽略。这也是为什么配置里 set_real_ip_from 必须写 CDN 的节点网段,而不是图省事写 0.0.0.0/0。
宝塔用的是自带的 Nginx,ngx_http_realip_module 默认已编译进去,可以直接用。配置位置有两个选择:写进站点配置文件(面板里「网站 → 设置 → 配置文件」),或者写进全局的 nginx.conf 的 http 段。如果只有个别站点走 CDN,放站点里更清晰;如果整台机器都在 CDN 后面,放全局更省事。
关键点在于 set_real_ip_from 要覆盖该 CDN 全部回源网段,漏写一段就会出现「一部分日志是真 IP、一部分还是节点 IP」的怪现象。CDN 厂商的节点段会变动,建议从官方文档获取最新列表,或使用厂商提供的动态更新方式,具体以官方文档和实际环境为准。
# 站点配置或 nginx.conf 的 http 段内
只信任来自 CDN 回源节点的连接
set_real_ip_from 203.0.113.0/24;
set_real_ip_from 198.51.100.0/24;
依据哪个头来还原真实地址
real_ip_header X-Forwarded-For;
从 XFF 列表末尾往前找,跳过可信代理,取第一个不可信地址
real_ip_recursive on;
还原之后,$remote_addr 就是访客地址,
$realip_remote_addr 保留原始对端地址(CDN 节点)
log_format cdn_log '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'xff="$http_x_forwarded_for" node="$realip_remote_addr"';
access_log /www/wwwlogs/example.com.log cdn_log;
上面这段日志格式里同时保留了 $remote_addr(还原后的访客 IP)、$http_x_forwarded_for(完整转发链)和 $realip_remote_addr(原始对端,也就是 CDN 节点 IP)。三层信息都在,排查问题时不至于抓瞎——比如怀疑 CDN 没传头,看一眼 xff 字段是不是空的就能判断。
改完配置后记得测试并重载,不要直接重启打断连接:
nginx -t && nginx -s reload
宝塔面板也可以点「重载配置」,效果一致
验证:curl 带伪造头请求,看日志里 remote_addr 是否被改
curl -H 'X-Forwarded-For: 1.2.3.4' http://127.0.0.1/
这里有个容易踩的坑:如果你从本机 curl 测试,源 IP 是 127.0.0.1,不在 set_real_ip_from 列表里,real_ip 模块不会生效,日志里仍然显示 127.0.0.1。这是正常的,说明信任边界起作用了。想验证配置是否生效,应该从 CDN 节点侧发起请求,或者临时把测试机 IP 加进 set_real_ip_from 再撤掉。
另外,宝塔面板的「网站监控报表」、防火墙类插件读取的也是 Nginx 传递的地址,real_ip 配好之后这些统计才会准。如果插件有自己的 IP 获取逻辑,需要按插件文档单独配置,不要想当然认为改一处就全好了。
实际环境里经常不止一层:访客 → CDN → 你自己的另一台反代 Nginx → 源站。这时 X-Forwarded-For 会变成「访客IP, CDN节点IP, 反代IP」这样的三段甚至更多。real_ip_recursive on 的作用就是从列表末尾往前扫,跳过所有出现在 set_real_ip_from 里的可信地址,遇到第一个不可信的,就把它当作真实客户端地址。
所以多层场景下的正确做法是:把所有可信代理的地址段全部写进 set_real_ip_from,包括 CDN 回源段和你自己那台反代的 IP。哪一层漏了,取值就会停在那一段,日志里看到的要么是反代 IP,要么是 CDN 节点 IP。
如果 real_ip_recursive 关掉(默认 off),模块会直接取 X-Forwarded-For 的最后一段,在只有一层 CDN 时结果一样,但多层下就会取到离源站最近的那个代理地址,明显是错的。所以多层代理场景建议显式打开这个开关。
还有一种情况是 CDN 同时传了 X-Forwarded-For 和 X-Real-IP,两者不一致。这时 real_ip_header 写哪个就以哪个为准。稳妥的做法是先抓一次回源请求看头,或者查阅厂商文档确认它到底传哪个头、XFF 里包含几段,再决定配置。不同厂商行为有差异,以官方文档为准。
最后提醒一句:real_ip 只解决「日志和 Nginx 变量里的地址」,它不会自动让 PHP 里的 $_SERVER['REMOTE_ADDR'] 变成真实 IP。如果你的程序需要拿到访客地址,还得在 PHP 层根据可信代理判断后读取 HTTP_X_FORWARDED_FOR,或者让 Nginx 通过 fastcgi_param 把还原后的地址传下去。这块要按程序的实际情况处理。
小结一下:开 CDN 后日志显示节点 IP 是预期现象,根因是 Nginx 只认 TCP 对端。配好 set_real_ip_from 信任列表 + real_ip_header 指定头 + real_ip_recursive on,再用带 xff 和 realip_remote_addr 的 log_format 记录,日志就能同时保留访客地址、转发链和节点地址三层信息。配置完成后先 nginx -t 再 reload,用真实 CDN 请求验证,别拿本机 curl 的结果下结论。下一步建议顺手检查一下程序层获取 IP 的方式,把限流、封禁、统计这几处一起对齐,才算真正把真实 IP 这件事做完整。
| 📑 | 📅 |
|---|---|
| Nginx 静态资源 304 与 200 反复切换:条件请求排查 | 2026-09-22 |
| 宝塔面板SSL后www与裸域只生效一个:证书覆盖与server_name分流 | 2026-09-21 |
| 服务器买多大够用:按日均PV估算CPU内存带宽 | 2026-09-21 |
| 域名转入转出实操:转移码、60天锁与DNS不断线 | 2026-09-21 |
| 宝塔面板新建站点访问却是默认页:根目录与index排查 | 2026-09-21 |
| WordPress数据库utf8转utf8mb4:emoji问号的处理步骤 | 2026-09-22 |
| 服务器只开80/443:SSH端口转发访问面板与数据库 | 2026-09-22 |
| 换服务器后百度收录掉了:改IP前后的抓取诊断与sitemap动作 | 2026-09-22 |
| Nginx 413 上传被拦:client_max_body_size 与多层限制联动排查 | 2026-09-23 |
| WordPress站点健康提示REST API出错:loopback请求失败逐项排查 | 2026-09-23 |