应通过可信代理传递的HTTP头(如X-Real-IP、CF-Connecting-IP)获取真实IP,严格校验代理来源和IP格式,避免直接信任REMOTE_ADDR或未验证的X-Forwarded-For。

$_SERVER['REMOTE_ADDR'] 只能拿到最外层代理的 IP
直接读 $_SERVER['REMOTE_ADDR'] 在绝大多数生产环境里是错的——你拿到的其实是 Nginx、CDN 或负载均衡器的出口 IP,不是用户真实出口。比如用 Cloudflare、阿里云 SLB、腾讯云 CLB 后,$_SERVER['REMOTE_ADDR'] 就是它们的机器地址,不是访客手机或电脑的出口 IP。
真正要靠的是代理链中传递的 HTTP 头字段,但这些头可以被客户端伪造,所以不能无条件信任。
实操建议:
- 只信任你可控的上游代理(如你自己的 Nginx)设置的头,比如
X-Real-IP或X-Forwarded-For - Nginx 配置里必须显式加
proxy_set_header X-Real-IP $remote_addr;,否则 PHP 根本收不到真实源 IP - 如果用了多层代理(例如 CDN → Nginx → PHP),
X-Forwarded-For是逗号分隔的字符串,最右边才是原始客户端 IP,但前提是每一层都正确追加且未被污染
别直接用 $_SERVER['HTTP_X_FORWARDED_FOR'] 做判断
这个值来自请求头,用户只要 curl -H "X-Forwarded-For: 1.2.3.4" 就能伪造。直接用它做风控、限流、日志记录,等于把门钥匙交给攻击者。
立即学习“PHP免费学习笔记(深入)”;
安全做法是:只在确认请求确实经过可信代理后,才从该头提取 IP,并且只取「可信跳数」之后的那个地址。
实操建议:
- 先检查
$_SERVER['REMOTE_ADDR']是否属于你配置的可信代理网段(比如 Nginx 所在内网 IP 段) - 再解析
$_SERVER['HTTP_X_FORWARDED_FOR'],用array_filter(array_map('trim', explode(',', $ipStr)))拆出 IP 列表 - 取倒数第 N 个 IP(N = 你控制的可信代理层数),例如只有一层 Nginx,则取最后一个;有 CDN + Nginx,则取倒数第二个
- 对取出的 IP 调用
filter_var($ip, FILTER_VALIDATE_IP)做基础校验,排除明显非法格式
PHP 7.3+ 可用 getallheaders() 但注意 SAPI 差异
getallheaders() 能读到所有请求头,在 Apache 下稳定可用,但在 CLI、FPM、Nginx+php-fpm 组合下可能返回空或报错,因为不是所有 SAPI 都实现该函数。
更兼容的写法是手动从 $_SERVER 提取,比如 $_SERVER['HTTP_X_REAL_IP'] 或 $_SERVER['HTTP_X_FORWARDED_FOR'](注意键名全大写、HTTP_ 前缀、连字符变下划线)。
实操建议:
- 不要依赖
getallheaders(),改用isset($_SERVER['HTTP_X_REAL_IP']) ? $_SERVER['HTTP_X_REAL_IP'] : null - 如果 Nginx 设置了
underscores_in_headers on;,记得关掉,否则含下划线的头(如X_Real_IP)会被丢弃 - 验证时别漏掉 IPv6 地址,
filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)要单独判
Cloudflare 等 CDN 场景必须用 CF-Connecting-IP
Cloudflare、StackPath 这类 CDN 不走标准 X-Forwarded-For,而是用自己签名的头,比如 CF-Connecting-IP。而且它只在你开启“真实 IP”插件并完成源站验证后才有效,否则仍是伪造的。
这个头无法被访客覆盖(Cloudflare 会清除客户端传来的同名头),比 X-Forwarded-For 可信得多。
实操建议:
- 优先检查
$_SERVER['HTTP_CF_CONNECTING_IP'],存在且非空就直接用 - 配合 Cloudflare 的 IP 段列表做白名单校验(官方定期更新:https://www.php.cn/link/2043d2a8fa2208b4c5f19bc6d5a94320),确认请求确实来自 Cloudflare
- 如果你同时用了自建 Nginx 和 Cloudflare,Nginx 的
set_real_ip_from必须包含 Cloudflare 的全部 IP 段,否则REMOTE_ADDR无法还原
X-Forwarded-For,或者忘了 Nginx 侧没配 real_ip_header 导致 PHP 层永远拿不到真实值。











