$_server['remote_addr']仅返回直接连接的上一跳ip,非真实客户端ip;需结合可信代理头如http_x_real_ip或http_cf_connecting_ip,并校验ip格式与来源可信性。

直接用 $_SERVER['REMOTE_ADDR'] 只能拿到最外层代理的 IP,不是真实客户端 IP;要拿真实 IP,必须结合 $_SERVER['HTTP_X_FORWARDED_FOR'] 或 $_SERVER['HTTP_X_REAL_IP'],但不能无条件信任——它们全可被伪造。
为什么 $_SERVER['REMOTE_ADDR'] 不够用
它只返回与当前服务器建立 TCP 连接的上一跳地址。如果用户经过 Nginx、CDN、负载均衡或正向代理(比如公司出口网关),这里拿到的就是那个中间节点的 IP,不是用户手机或电脑的真实出口 IP。
- 直连时:值为用户真实公网 IP
- 经 Nginx 反代且未配置
proxy_set_header X-Real-IP $remote_addr:仍是 Nginx 本机 IP(如127.0.0.1) - 经 CDN(如 Cloudflare、阿里云 DDoS 防护):通常是 CDN 节点 IP,真实 IP 在
HTTP_CF_CONNECTING_IP或HTTP_X_FORWARDED_FOR中
怎么安全地提取真实 IP(推荐逻辑)
不能只取 HTTP_X_FORWARDED_FOR 的第一个值,也不能盲目相信所有头字段。必须满足两个前提:① 你明确知道请求路径中哪些代理是可信的(比如你自己的 Nginx);② 只从这些可信代理传来的头里取值。
- 如果你用 Nginx 做反向代理,且在配置里加了
proxy_set_header X-Real-IP $remote_addr,优先读$_SERVER['HTTP_X_REAL_IP'] - 如果用了 Cloudflare,且启用了「True Client IP」功能,读
$_SERVER['HTTP_CF_CONNECTING_IP'](它不可伪造) - 只有在你控制的可信代理链中才考虑
HTTP_X_FORWARDED_FOR,并只取最右端非私有地址(如去掉127.0.0.1, 192.168.1.100, 10.0.0.5后剩下的第一个公网 IP) - 永远校验 IP 格式:
filter_var($ip, FILTER_VALIDATE_IP),拒绝 IPv6 混合格式或含端口的字符串
一个轻量但实用的获取函数示例
以下函数假设你只有一层可信 Nginx 反代(即 X-Real-IP 是可靠的),同时兼容 Cloudflare:
立即学习“PHP免费学习笔记(深入)”;
function get_client_ip() {
if (!empty($_SERVER['HTTP_CF_CONNECTING_IP'])) {
return $_SERVER['HTTP_CF_CONNECTING_IP'];
}
if (!empty($_SERVER['HTTP_X_REAL_IP'])) {
$ip = $_SERVER['HTTP_X_REAL_IP'];
if (filter_var($ip, FILTER_VALIDATE_IP)) {
return $ip;
}
}
if (!empty($_SERVER['REMOTE_ADDR'])) {
return $_SERVER['REMOTE_ADDR'];
}
return '0.0.0.0';
}
注意:不要在函数里自动解析 X-Forwarded-For 多级 IP 列表——除非你严格限制了上游代理白名单,否则极易被绕过。
常见错误和线上踩坑点
很多线上故障都源于 IP 获取逻辑写得太“乐观”:
- 直接
explode(',', $_SERVER['HTTP_X_FORWARDED_FOR'])[0]→ 攻击者发X-Forwarded-For: 1.1.1.1, 2.2.2.2, 127.0.0.1就能伪造内网 IP - 没做
FILTER_VALIDATE_IP校验 → 输入X-Real-IP: 127.0.0.1<script></script>可能导致日志注入或后续逻辑误判 - 把
$_SERVER['HTTP_X_FORWARDED_FOR']当作唯一依据,却没关掉外部可写该 Header 的入口(比如某些 WAF 允许用户自定义 Header) - 在 CLI 模式下运行该函数(如 PHPUnit 测试)→
$_SERVER里根本没有这些键,会触发 Notice
真实环境里,IP 字段是攻击面之一,越早收敛可信来源,后期排查越省力。别让“简单取个 IP”变成日志污染、限流失效或风控误杀的起点。











