根本原因是Nginx默认不透传真实IP头且phpEnv未启用realip模块;必须同时配置Nginx的proxy_set_header(X-Real-IP和X-Forwarded-For)与PHP的正确读取逻辑(优先HTTP_X_REAL_IP,再fallback解析X_FORWARDED_FOR首IP),缺一不可。

直接说结论:在 phpEnv 环境中用 Nginx 做反向代理时,后端 PHP 拿不到真实 IP 的根本原因,是 Nginx 默认不往请求头里塞 X-Forwarded-For,也不是所有 phpEnv 版本默认启用 realip 模块。必须手动配两处:Nginx 转发头 + PHP 读取逻辑,缺一不可。
nginx.conf 里必须加 proxy_set_header
phpEnv 自带的 Nginx 配置(比如 vhost 目录下的站点配置)默认没有透传真实 IP 的头。你得在 location / 块里显式添加:
-
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;—— 这行最关键,它把客户端 IP 加到X-Forwarded-For头末尾;如果中间还有其他代理(比如 CDN),这个值会变成client_ip, cdn_ip -
proxy_set_header X-Real-IP $remote_addr;—— 简单粗暴,只传当前直连 Nginx 的 IP(即用户或上一级代理的 IP) -
proxy_set_header Host $host;—— 避免后端因 Host 错乱导致跳转或 HTTPS 判断失败
注意:$proxy_add_x_forwarded_for 和 $remote_addr 不是一回事:$remote_addr 是 TCP 连接来源,$proxy_add_x_forwarded_for 是“继承旧值 + 追加新值”,更安全可靠。
PHP 代码不能只读 $_SERVER['REMOTE_ADDR']
没配 Nginx 透传时,$_SERVER['REMOTE_ADDR'] 永远是 127.0.0.1 或 phpEnv 所在服务器的内网 IP;配了之后也得改 PHP 读取方式,否则还是拿不到。
立即学习“PHP免费学习笔记(深入)”;
- 优先读
$_SERVER['HTTP_X_REAL_IP'](对应 Nginx 的X-Real-IP头),它最干净,只有一个 IP - fallback 到
$_SERVER['HTTP_X_FORWARDED_FOR'],但必须用explode(',', ...)[0]取第一个 —— 因为攻击者可能伪造该头,而可信链路上第一个才是客户端真实 IP(前提是 Nginx 配了set_real_ip_from) - 绝对不要直接用
$_SERVER['REMOTE_ADDR']做风控或日志,它在这里毫无意义
典型判断逻辑:
function getClientIp() {
if (!empty($_SERVER['HTTP_X_REAL_IP'])) {
return $_SERVER['HTTP_X_REAL_IP'];
}
if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$ips = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
return trim($ips[0]);
}
return $_SERVER['REMOTE_ADDR'];
}
phpEnv 默认不启用 realip 模块,别指望 access_log 自动记真实 IP
即使你加了 proxy_set_header,Nginx 的 access_log 默认仍记 $remote_addr(即代理 IP)。想让日志里也显示真实 IP,得启用 realip 模块并配置:
- 先确认模块已编译:
nginx -V 2>&1 | grep -o with-http_realip_module,没输出说明没启用 - 在
http块加配置:set_real_ip_from 127.0.0.1;(phpEnv 里 Nginx 和 PHP-FPM 通常同机,可信源就是本机) -
real_ip_header X-Real-IP;(和你proxy_set_header X-Real-IP对应) - 自定义 log_format,把
$remote_addr换成$realip_remote_addr
不配这个,access.log 里看到的永远是 127.0.0.1,排查问题时会误判流量来源。
CDN 或多级代理下 X-Forwarded-For 顺序容易搞反
如果你的 phpEnv 前面还套了腾讯云 CDN、阿里云 WAF 或公司自建 LB,X-Forwarded-For 可能是 client, cdn, waf 这样的三段。这时候 PHP 里取 [0] 是对的,但 Nginx 的 realip 模块必须设 real_ip_recursive on,否则它会从右往左找,把 waf 当成客户端。
- 单层代理(仅 phpEnv/Nginx):
real_ip_recursive off也行,因为只有一个 IP - 两层及以上(Nginx ← CDN ← client):
real_ip_recursive on+set_real_ip_from列出所有可信代理 IP 段 - 千万别写
set_real_ip_from 0.0.0.0/0,这等于把伪造X-Forwarded-For的入口完全打开
真实 IP 获取不是“配完就完事”,它依赖整条链路每个环节的信任边界是否清晰 —— 最容易被忽略的是:你信任谁,决定了你最终拿到的是谁的 IP。



















