remote_addr 最可靠,是操作系统提供的真实连接ip;http_client_ip 易被伪造,不可信;安全获取用户ip需校验x-forwarded-for中非私有且不在可信代理列表的最右ip。

HTTP_CLIENT_IP 和 REMOTE_ADDR 都能拿到客户端 IP,但它们来源完全不同,**不能互换,也不能简单取一个就完事**——多数情况下直接用 REMOTE_ADDR 最可靠,而 HTTP_CLIENT_IP 几乎总是不可信、易被伪造。
为什么 HTTP_CLIENT_IP 基本不可信
这个值来自 HTTP 请求头中的 X-Forwarded-For 或 X-Real-IP(具体取决于 Web 服务器配置),但 PHP 并不自动解析这些头为 HTTP_CLIENT_IP;它只是把原始请求头里叫 Client-IP 的字段原样抄过来——而这个字段完全由客户端或中间代理控制。
- 浏览器、curl、Postman 都能手动加
Client-IP: 1.2.3.4头,$_SERVER['HTTP_CLIENT_IP']就会返回这个假 IP - Nginx/Apache 默认根本不会设置
Client-IP头,所以该变量常为空或不存在 - 即使你写了
proxy_set_header Client-IP $remote_addr;,也只是在反向代理时“主动设”了它,不代表它安全——上游若没校验,仍可能被绕过
REMOTE_ADDR 是唯一真实可靠的连接端 IP
这是 PHP 从底层 socket 连接中读取的对端地址,由操作系统提供,**无法被 HTTP 请求头篡改**。它代表与当前 Web 服务器(或 PHP-FPM)建立 TCP 连接的那个 IP。
- 直连场景下,就是用户真实出口 IP(如家庭宽带公网 IP)
- 有反向代理(Nginx → PHP-FPM)时,
REMOTE_ADDR是 Nginx 的内网 IP(如127.0.0.1或192.168.1.10),不是用户 IP —— 这是正常现象,不是 bug - 想在这种架构下拿到用户真实 IP,必须靠代理显式传递(如
X-Forwarded-For),再由 PHP 主动读取并校验,而不是依赖HTTP_CLIENT_IP
真正安全获取用户 IP 的做法(带校验)
别拼凑 HTTP_CLIENT_IP、HTTP_X_FORWARDED_FOR、REMOTE_ADDR 然后直接用。得先确认哪些 IP 是可信的(比如你的 Nginx 服务器内网段),再从可信链中提取最左/最后一个非私有 IP。
立即学习“PHP免费学习笔记(深入)”;
$trustedProxies = ['127.0.0.1', '192.168.1.0/24'];
$ip = $_SERVER['REMOTE_ADDR'];
if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$ips = array_map('trim', explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']));
// 从右往左找第一个不在可信列表里的 IP(即用户真实出口)
foreach (array_reverse($ips) as $candidate) {
if (!in_array($candidate, $trustedProxies) && filter_var($candidate, FILTER_VALIDATE_IP)) {
$ip = $candidate;
break;
}
}
}
// $ip 现在是相对可信的用户 IP
- 永远以
REMOTE_ADDR为 fallback,它永远不会是空或伪造的(除非 socket 层出问题) -
HTTP_X_FORWARDED_FOR比HTTP_CLIENT_IP更常见、更可控,但依然要白名单校验 - 别用
$_SERVER['HTTP_X_REAL_IP']代替校验——它只是 Nginx 设置的一个头,一样可被伪造,除非你知道它只来自你信任的代理且未被二次转发
真正难的不是“怎么取 IP”,而是判断“哪个环节可以信任”。一旦用了 CDN、多层 LB、K8s Ingress,X-Forwarded-For 可能被追加多次,REMOTE_ADDR 可能变成服务网格内部地址——这时候光看 PHP 变量没用,得查整个流量路径上每一跳是否设置了可信头、是否做了 IP 白名单校验。











