搜索全站
安装部署 11 分钟阅读

反向代理后 WordPress 取不到真实 IP 的解决方法

用了 CDN 或 Nginx 反向代理后,WordPress 评论、日志里所有访客 IP 都变成同一个。本文讲清 X-Forwarded-For 的传递原理,给出 Nginx、Cloudflare、宝塔三种场景的修正代码。

U
UFO_PF 2024-02-07 · 阅读 11 分钟
反向 安装部署
阅读提示
本文约 4,373 字,预计阅读 11 分钟。文中命令与配置均经过实测,可直接参考执行。

引言:所有访客 IP 都一样了

站点接入 Cloudflare、阿里云 CDN,或者在前面加了一层 Nginx 反向代理之后,你可能会发现:

  • 后台评论列表里,所有评论的 IP 都是 172.69.x.x 或 127.0.0.1;
  • 装了安全插件,封 IP 时把 CDN 节点给封了,导致全站打不开;
  • 表单插件记录的地域统计全是服务器机房所在地;
  • 限流插件失效,因为所有请求看起来来自同一个地址。

这不是 WordPress 的问题,是网络结构变了但代码没跟着变。

原理分析:REMOTE_ADDR 是谁

PHP 里的 $_SERVER['REMOTE_ADDR'] 是 TCP 连接的对端地址。加了一层代理后,和 PHP-FPM 建立连接的不是访客,而是代理服务器本身,所以 REMOTE_ADDR 永远等于代理的内网 IP。

代理服务器知道访客的真实 IP,它通过 HTTP 头把信息传递下去,业界约定俗成的头字段有:

头字段 提出者 格式
X-Forwarded-For 事实标准 client, proxy1, proxy2(逗号分隔,第一个是真实客户端)
X-Real-IP Nginx 常用 单个 IP
CF-Connecting-IP Cloudflare 单个 IP
True-Client-IP Cloudflare 企业版 / Akamai 单个 IP
X-Forwarded-Proto — http 或 https(协议)

安全风险:X-Forwarded-For 是客户端可伪造的。如果服务器无条件信任它,攻击者可以随意伪造 IP 绕过封禁。所以正确做法是:只信任你自己的代理层,即只接受来自可信代理 IP 的转发头,且剥离客户端自己带的同名头。

保姆级实操

场景一:自建 Nginx 反向代理(前端 Nginx → 后端 Nginx + PHP-FPM)

前端代理配置:

location / {
    proxy_pass http://backend;
    proxy_set_header Host              $host;
    proxy_set_header X-Real-IP         $remote_addr;
    proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
}

后端站点的 PHP 配置(关键):

# 在 server 块里定义可信代理网段
set_real_ip_from 10.0.0.0/8;      # 你的内网 / 代理 IP
set_real_ip_from 127.0.0.1;
real_ip_header   X-Forwarded-For;
real_ip_recursive on;

real_ip_recursive on 的作用是:从右往左剥离可信代理的 IP,剩下最左边那个就是真实客户端。这样 PHP 拿到的 $_SERVER['REMOTE_ADDR'] 直接就是真实 IP,所有插件都自动正确,不需要改任何 PHP 代码。这是最优雅的方案。

场景二:Cloudflare(含免费版)

如果源站是 Nginx,用 Cloudflare 官方 IP 段:

# 生成可信代理配置
sudo tee /etc/nginx/conf.d/cloudflare_realip.conf > /dev/null <<'EOF'
EOF
for ip in $(curl -s https://api.cloudflare.com/client/v4/ips | \
  C:/Users/Alex/.workbuddy/binaries/python/versions/3.13.12/python.exe -c "import sys,json;d=json.load(sys.stdin);print('\n'.join(d['result']['ipv4_cidrs']))"); do
  echo "set_real_ip_from $ip;" | sudo tee -a /etc/nginx/conf.d/cloudflare_realip.conf
done
echo "real_ip_header CF-Connecting-IP;" | sudo tee -a /etc/nginx/conf.d/cloudflare_realip.conf

上面这段建议在 shell 里分步执行,最终文件内容形如:

set_real_ip_from 173.245.48.0/20;
set_real_ip_from 103.21.244.0/22;
# ... 若干条 Cloudflare 段
real_ip_header CF-Connecting-IP;

Cloudflare 的 IP 段会变,建议加个季度更新的 cron:

0 4 1 */3 * curl -s https://api.cloudflare.com/client/v4/ips | \
  C:/Users/Alex/.workbuddy/binaries/python/versions/3.13.12/python.exe -c "
import sys,json
d=json.load(sys.stdin)
print('\n'.join('set_real_ip_from %s;'%i for i in d['result']['ipv4_cidrs']))
print('real_ip_header CF-Connecting-IP;')
" > /etc/nginx/conf.d/cloudflare_realip.conf && nginx -t && systemctl reload nginx

场景三:无法改 Nginx(虚拟主机 / 宝塔简易模式)

在 wp-config.php 顶部(必须在 /* That's all */ 之前)加:

/**
 * 从代理头还原真实 IP
 * 仅在检测到可信代理头时才覆盖,避免本地直连时 IP 变空
 */
if ( ! empty( $_SERVER['HTTP_CF_CONNECTING_IP'] ) ) {
    $_SERVER['REMOTE_ADDR'] = $_SERVER['HTTP_CF_CONNECTING_IP'];
} elseif ( ! empty( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ) {
    // 取逗号分隔的第一个
    $xff = explode( ',', $_SERVER['HTTP_X_FORWARDED_FOR'] );
    $xff = trim( $xff[0] );
    if ( filter_var( $xff, FILTER_VALIDATE_IP ) ) {
        $_SERVER['REMOTE_ADDR'] = $xff;
    }
}

同时告诉 WordPress 当前是 HTTPS(否则会出现重定向循环):

if ( ! empty( $_SERVER['HTTP_X_FORWARDED_PROTO'] )
     && 'https' === $_SERVER['HTTP_X_FORWARDED_PROTO'] ) {
    $_SERVER['HTTPS'] = 'on';
}

场景四:修复历史数据

已经存进数据库的错误 IP 不会自动更正,可以用 SQL 批量修正(谨慎,先备份):

-- 查看受影响评论数量
SELECT COUNT(*) FROM wp_comments WHERE comment_author_IP = '127.0.0.1';

历史 IP 无法还原(真实 IP 没被记录),只能从 Web 服务器访问日志里反查。所以先修配置再谈数据。

常见踩坑

  1. 加了代码反而 IP 变成空:HTTP_X_FORWARDED_FOR 在 PHP 里的键名是 HTTP_ 前缀 + 横线变下划线,别写成 HTTP_X_FORWARDED_FOR 之外的形式;另外本地直连时没有这个头,必须判空。
  2. 出现重定向循环:只还原了 IP 没处理 HTTPS。Cloudflare 的 “Flexible” 模式下,回源是 HTTP,前端是 HTTPS,WordPress 以为自己没开 HTTPS 就一直 301。用上面的 X-Forwarded-Proto 判断,或者把 Cloudflare 加密模式改成 “Full (strict)”。
  3. 封 IP 把 CDN 封了:Wordfence、iThemes Security 之类的插件有”检测代理”选项,默认关闭。开启后插件才会读转发头。否则它封的是 CDN 节点,等于全站封死。
  4. 案例:某站用了两层代理(外层 CDN,内层 Nginx),X-Forwarded-For 里是 真实IP, CDN节点IP。站长在代码里取 explode(',')[0] 拿到了正确 IP,但安全插件读的是最后一个,结果封禁逻辑全错。多层代理下必须明确”取第几个”,用 real_ip_recursive on 让 Nginx 帮你算最省心。
  5. 伪造 IP 绕过限制:任何人都能发 X-Forwarded-For: 8.8.8.8。如果你的站有 IP 白名单功能,务必配合 set_real_ip_from 限定可信源。

总结

优先级排序:能用 Nginx 的 ngx_http_realip_module 就别改 PHP(一次配置,全站所有插件受益);改不了服务器再退而求其次改 wp-config.php;Cloudflare 用户记得同时处理 X-Forwarded-Proto 防止重定向循环。无论哪种方案,都要遵守”只信任可信代理”这一条安全底线。配好后,去后台看评论列表的 IP 是否已经变成真实地址,再用手机 4G 访问一次确认。关于代理环境下的缓存配合,可参考反向代理与页面缓存的注意事项。

U
UFO_PF作者

专注 WordPress 生态的实战作者,内容以「可复制、可验证」为第一原则。

留下你的想法

邮箱地址不会被公开。带 * 的为必填项。