$_SERVER['HTTP_X_REAL_IP']经常为空,因为Nginx默认不设置X-Real-IP头,且PHP-FPM会过滤含下划线的HTTP头;需在Nginx中同时配置proxy_set_header和fastcgi_param才能使其生效。

PHP 里直接读 $_SERVER['HTTP_X_REAL_IP'] 是无效的,除非 Nginx 明确转发了这个头,且 PHP 没被 CGI/FPM 的安全机制过滤掉。
为什么 $_SERVER['HTTP_X_REAL_IP'] 经常为空?
Nginx 默认不会自动设置 X-Real-IP 请求头;即使你写了 proxy_set_header X-Real-IP $remote_addr;,PHP 的 FPM 或 CGI 模式也可能因 security.limit_extensions 或 env 配置限制,不把带下划线的 HTTP 头暴露给 $_SERVER 数组。
- FPM 下,
HTTP_X_REAL_IP这类变量名会被转成小写并用下划线连接,但某些旧版 FPM(如 7.0 前)会直接丢弃含下划线的头 - Apache + mod_php 不受此限,但 Nginx + PHP-FPM 是主流场景,必须主动适配
-
浏览器或中间代理可能伪造
X-Real-IP,不能无条件信任
Nginx 必须做的三件事
仅加一行 proxy_set_header 不够。要让 PHP 稳定拿到真实 IP,Nginx 配置需同时满足:
- 在
location或server块中添加:proxy_set_header X-Real-IP $remote_addr; - 补上
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;(便于链路追踪) - 关键:在
fastcgi_param中显式传递——在fastcgi_params文件或 server 块里加:fastcgi_param HTTP_X_REAL_IP $remote_addr;
最后一行才是让 $_SERVER['HTTP_X_REAL_IP'] 出现的真正原因:它绕过了 FPM 对请求头的自动转换逻辑,直接注入环境变量。
立即学习“PHP免费学习笔记(深入)”;
PHP 中安全获取真实 IP 的推荐写法
别只依赖 $_SERVER['HTTP_X_REAL_IP']。应按可信层级降序判断,并校验是否为合法公网 IP:
// 只信任来自本机 Nginx 的转发(防止伪造) $trusted_proxies = ['127.0.0.1', '::1'];$client_ip = $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0'; if (in_array($client_ip, $trusted_proxies, true)) { $x_real_ip = $_SERVER['HTTP_X_REAL_IP'] ?? ''; if (filter_var($x_real_ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) { $client_ip = $x_real_ip; } } // 最终 $client_ip 即为较可信的真实客户端 IP
注意:FILTER_FLAG_NO_PRIV_RANGE 排除内网段,FILTER_FLAG_NO_RES_RANGE 排除保留地址(如 0.0.0.0、255.255.255.255),避免被恶意头欺骗。
调试时怎么看头有没有传进来?
临时加一段诊断代码,比查日志更快:
error_log('X-Real-IP header: ' . ($_SERVER['HTTP_X_REAL_IP'] ?? '(not set)'), 4);
error_log('All HTTP_* keys: ' . json_encode(array_keys($_SERVER)), 4);
然后看 PHP 错误日志(不是 Nginx access log)。如果 HTTP_X_REAL_IP 不在输出里,说明 FPM 没收到;如果在但值不对,说明 Nginx 没正确设置 fastcgi_param 或用了错误的变量(比如写成 $http_x_real_ip)。
真实 IP 获取的关键不在 PHP 怎么读,而在于 Nginx 是否通过 fastcgi_param 把它“塞”进了 PHP 的运行环境——这个步骤最容易被文档跳过,也最难排查。











