$_SERVER['REMOTE_ADDR']总是内网IP,因其仅反映直接连接PHP进程的代理(如Nginx或CDN节点)IP;真实用户IP需依赖可信header(如X-Real-IP、CF-Connecting-IP)并校验代理IP段后提取。
![为什么php环境下使用$_server[\'remote_addr\']获取的是内网ip_检查代理转发](https://img.php.cn/upload/article/000/969/633/177751521755021.png?x-oss-process=image/resize,p_40)
因为 $_SERVER['REMOTE_ADDR'] 拿到的是 TCP 连接发起方的 IP,不是用户真实出口 IP —— 在有 Nginx、CDN 或负载均衡时,它必然变成内网地址。
$_SERVER['REMOTE_ADDR'] 为什么总是内网 IP
这个值由 PHP 的 SAPI 层(如 FPM)从 socket 连接中直接读取,只反映「谁在跟当前 PHP 进程说话」。如果请求路径是「用户 → CDN → Nginx → PHP」,那 $_SERVER['REMOTE_ADDR'] 就是 Nginx 所在机器的内网 IP(比如 10.0.1.5 或 172.18.0.3),而不是用户手机或宽带拨号出来的公网 IP。
常见表现:
- 本地开发用 Apache 直连,
$_SERVER['REMOTE_ADDR']看起来正常;一上生产环境就变成10.x.x.x或172.x.x.x - 用了 Cloudflare,
$_SERVER['REMOTE_ADDR']是 Cloudflare 节点 IP(如104.28.2.17),但这个 IP 不属于你可控网段,不能直接当真实用户 IP 用 - Docker 部署时,Nginx 和 PHP-FPM 分属不同容器,
$_SERVER['REMOTE_ADDR']固定为 Nginx 容器的 docker 网络地址
检查 Nginx 是否正确转发了原始 IP
Nginx 默认不会把用户真实 IP 透传给后端,必须手动配置。PHP 要拿到真实 IP,前提是 Nginx 显式设置了对应 header。
确认以下两点:
- 你的 Nginx
location或server块里有:proxy_set_header X-Real-IP $remote_addr; - 同时建议加:
proxy_set_header X-Forwarded-For $remote_addr;(注意不是$proxy_add_x_forwarded_for,后者会拼接,容易被污染) - 如果用了多层代理(比如 CDN + Nginx),确保 CDN 也把原始 IP 写入某个可信 header(如 Cloudflare 的
CF-Connecting-IP)
没配这些,PHP 就根本收不到原始 IP,后续所有逻辑都白搭。
为什么不能直接用 $_SERVER['HTTP_X_FORWARDED_FOR']
这个值来自 HTTP 请求头,客户端可随意伪造:curl -H "X-Forwarded-For: 1.2.3.4" https://yoursite.com 就能让 PHP 收到 1.2.3.4 —— 所以它必须和 $_SERVER['REMOTE_ADDR'] 联动校验。
安全用法要点:
- 先判断
$_SERVER['REMOTE_ADDR']是否落在你信任的代理网段内(如10.0.0.0/8、172.16.0.0/12) - 只在这个前提下,才解析
$_SERVER['HTTP_X_FORWARDED_FOR'],并取最后一个非私有 IP(array_pop($ips)后再filter_var(..., FILTER_VALIDATE_IP)) - 若用了 Cloudflare,优先读
$_SERVER['HTTP_CF_CONNECTING_IP'],它由 Cloudflare 签名保证不可伪造
一个最小可用的真实 IP 提取函数
不依赖框架,不引入外部库,只处理你可控的单层 Nginx 代理场景:
function get_real_client_ip(): ?string
{
$remoteAddr = $_SERVER['REMOTE_ADDR'] ?? '';
$xForwardedFor = $_SERVER['HTTP_X_FORWARDED_FOR'] ?? '';
$cfIp = $_SERVER['HTTP_CF_CONNECTING_IP'] ?? '';
// Cloudflare 可信,直接用
if ($cfIp && filter_var($cfIp, FILTER_VALIDATE_IP)) {
return $cfIp;
}
// 只有 REMOTE_ADDR 是内网地址时,才信任 X-Forwarded-For
$trustedRanges = ['10.0.0.0/8', '172.16.0.0/12', '192.168.0.0/16'];
if (ip_in_range($remoteAddr, $trustedRanges) && $xForwardedFor) {
$ips = array_map('trim', explode(',', $xForwardedFor));
$ip = end($ips);
if (filter_var($ip, FILTER_VALIDATE_IP)) {
return $ip;
}
}
// 最后兜底:仅当 REMOTE_ADDR 是公网 IP 时才返回(直连场景)
if (filter_var($remoteAddr, FILTER_VALIDATE_IP) && !filter_var($remoteAddr, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
return $remoteAddr;
}
return null;
}
注意:ip_in_range() 需要自己实现或使用现成的 CIDR 校验逻辑。很多项目漏掉这一步,导致攻击者绕过代理判断,直接注入伪造 IP。
真正难的不是写几行代码,而是厘清你架构里每一层谁在设置 header、谁在消费 header、哪些 IP 段你敢无条件信——这些信息错一点,IP 就不准一点。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











