$_SERVER['REMOTE_ADDR'] 在 Nginx + PHP-FPM 下不等于真实客户端 IP,是因为 Nginx 默认以代理身份转发请求,使 REMOTE_ADDR 显示自身地址(如 127.0.0.1),而真实 IP 需通过 real_ip 模块配合 proxy_set_header 和 set_real_ip_from 等配置从可信 HTTP 头(如 X-Real-IP)提取并验证。

为什么 $_SERVER['REMOTE_ADDR'] 在 Nginx + PHP-FPM 下不等于真实客户端 IP
因为 Nginx 默认不会把原始请求的 IP 透传给后端 PHP-FPM,而是把自身作为代理,把连接来源(比如本机 127.0.0.1)当作 REMOTE_ADDR。真实 IP 被压在 HTTP 头里(如 X-Forwarded-For 或 X-Real-IP),PHP 默认不读这些头。
常见现象:$_SERVER['REMOTE_ADDR'] 永远是 127.0.0.1 或 ::1;$_SERVER['HTTP_X_FORWARDED_FOR'] 可能为空或为逗号分隔字符串(含代理链);$_SERVER['HTTP_X_REAL_IP'] 可能未设置。
- Nginx 必须显式用
proxy_set_header把真实 IP 注入请求头 - PHP 不会自动解析或覆盖
REMOTE_ADDR,需自行从可信头提取并校验 - 不能无条件信任
X-Forwarded-For—— 它可被客户端伪造,只应在 Nginx 确认可信代理后才使用
Nginx 配置:必须设置 proxy_set_header X-Real-IP 和 real_ip 指令
光加 proxy_set_header 不够,Nginx 还得用 real_ip 模块识别并替换 $remote_addr,否则 PHP-FPM 收到的仍是 Nginx 的连接地址。
确保 Nginx 编译时启用了 --with-http_realip_module(主流发行版包通常已启用)。
立即学习“PHP免费学习笔记(深入)”;
- 在
location ~ \.php$块内添加:proxy_set_header X-Real-IP $remote_addr;proxy_set_header X-Forwarded-For $remote_addr; - 在
http或server块中添加:set_real_ip_from 127.0.0.1;(若 PHP-FPM 与 Nginx 同机)real_ip_header X-Real-IP;(指定用哪个头还原$remote_addr) - 如果前端还有 Cloudflare、CDN 或负载均衡器,要把它们的出口 IP 加入
set_real_ip_from,并改用X-Forwarded-For或X-Forwarded-For最右非私有 IP(需配合real_ip_recursive on;)
PHP 中安全获取客户端 IP 的推荐写法
不要直接用 $_SERVER['HTTP_X_FORWARDED_FOR'] 或 $_SERVER['HTTP_X_REAL_IP'],必须结合 Nginx 的可信代理配置做白名单判断。
假设你已在 Nginx 中设定了 set_real_ip_from 127.0.0.1; 且启用了 real_ip_header X-Real-IP;,那么 PHP 中最简且安全的方式是:
// 优先取经 real_ip 模块处理后的 REMOTE_ADDR(即真实客户端 IP)
$client_ip = $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
// 若需兼容未启用 real_ip 的旧配置,再 fallback 到可信头
if (isset($_SERVER['HTTP_X_REAL_IP']) && filter_var($_SERVER['HTTP_X_REAL_IP'], FILTER_VALIDATE_IP)) {
$client_ip = $_SERVER['HTTP_X_REAL_IP'];
}
- 避免解析
X-Forwarded-For字符串 —— 它可能被伪造,且格式不统一(如"203.0.113.195, 198.51.100.1") - 永远用
filter_var(..., FILTER_VALIDATE_IP)校验,防止注入或非法值 - 若 Nginx 未启用
real_ip模块,$_SERVER['REMOTE_ADDR']就不可信,此时只能依赖X-Real-IP,但前提是 Nginx 确实设置了它且来源可控
验证是否生效:检查 Nginx 日志和 PHP 输出
光改配置不测试,等于没改。两个关键验证点:
- 在 Nginx
access_log中加入$remote_addr和$http_x_real_ip,看访问日志里是否出现真实公网 IP - 写个临时 PHP 文件输出:
var_dump($_SERVER['REMOTE_ADDR'], $_SERVER['HTTP_X_REAL_IP'], $_SERVER['HTTP_X_FORWARDED_FOR']); - 用
curl -H "X-Real-IP: 1.2.3.4" http://yoursite/test.php测试伪造头是否被忽略(应仍显示真实 IP,说明real_ip生效) - 若用 Docker 或反向代理链,注意容器网络地址(如
172.x.x.x)也需加入set_real_ip_from
最容易被忽略的是 real_ip 模块未启用或 set_real_ip_from 漏掉某一级代理 IP —— 这会导致 REMOTE_ADDR 始终不变,而你以为 PHP 代码有问题。











