$_server['remote_addr']在生产环境几乎总是错的,因为它只返回上一跳代理(如nginx、cloudflare)的ip,而非用户真实出口ip;需结合http_cf_connecting_ip、http_x_real_ip等可信头,并严格校验代理来源与ip合法性。

直接读 $_SERVER['REMOTE_ADDR'] 在生产环境几乎总是错的——它只返回上一跳代理(比如 Nginx、Cloudflare 节点)的 IP,不是用户真实出口 IP。
为什么 $_SERVER['REMOTE_ADDR'] 总是 127.0.0.1 或内网地址
现代 PHP 应用基本都跑在反向代理之后:Nginx 做入口、Cloudflare 做 CDN、阿里云 SLB 做负载均衡。这些组件会用自己的 IP 覆盖 $_SERVER['REMOTE_ADDR'],同时把原始 IP 放进请求头里传给后端。
- 常见现象:日志里全是
127.0.0.1、10.0.0.x、172.16.x.x,但实际访问量很大 - 根本原因:
$_SERVER['REMOTE_ADDR']是 TCP 连接对端地址,不是 HTTP 请求来源地址 - Docker、LNMP 套件、宝塔面板默认配置下,这个值基本不可信
哪些请求头可信?优先级怎么排
没有“绝对可信”的头,只有“在你可控部署结构下相对可信”的头。必须按可信度降序检查,并配合代理来源校验:
-
$_SERVER['HTTP_CF_CONNECTING_IP']:Cloudflare 专用头,只在开启 Cloudflare 且未被中间层覆盖时可用;不需解析,直接校验格式即可 -
$_SERVER['HTTP_X_REAL_IP']:Nginx 显式配置proxy_set_header X-Real-IP $remote_addr;后才存在;比X-Forwarded-For更难伪造 -
$_SERVER['HTTP_X_FORWARDED_FOR']:最常用也最危险——逗号分隔,可被客户端任意伪造;仅当$_SERVER['REMOTE_ADDR']属于你信任的代理 IP 段时才考虑使用 -
$_SERVER['HTTP_CLIENT_IP']:极少被标准代理设置,且极易被伪造,基本不建议用
怎么安全提取第一个合法 IP
不能无脑取 $_SERVER['HTTP_X_FORWARDED_FOR'] 的第一个值,也不能跳过校验直接赋值。核心是两步:确认代理可信 + 校验 IP 合法性。
- 先定义你的可信代理列表,例如:
$trustedProxies = ['127.0.0.1', '10.0.0.5', '172.16.0.0/12']; - 检查
$_SERVER['REMOTE_ADDR']是否在该列表中(用ip_in_range()判断 CIDR,别用简单字符串匹配) - 若可信,再依次检查:
HTTP_CF_CONNECTING_IP→HTTP_X_REAL_IP→HTTP_X_FORWARDED_FOR(取explode(',', ...)[0]后trim()) - 对每个候选 IP 调用
filter_var($ip, FILTER_VALIDATE_IP),并排除私有地址(127.0.0.0/8、10.0.0.0/8、192.168.0.0/16等)
容易被忽略的关键细节
Nginx 配置没配对,PHP 里再怎么写逻辑也没用;而 PHP 里漏掉 CIDR 校验或私有地址过滤,就等于把风控大门敞开。
-
proxy_set_header X-Real-IP $remote_addr;必须出现在 Nginx 的location或server块里,且要确保没被其他配置覆盖 -
getallheaders()在 php-fpm + Nginx 下大概率不可用,别依赖它;所有头都要从$_SERVER手动取,注意键名是HTTP_前缀 + 全大写 + 连字符变下划线 - Cloudflare 的 IPv4 段是动态更新的,硬编码 IP 段不如用官方推荐的
HTTP_CF_CONNECTING_IP头 - 多层代理(如 CDN → Nginx → PHP)时,
X-Forwarded-For最右才是原始 IP,但前提是每一层都正确追加——现实中更稳妥的做法是让 Nginx 用set_real_ip_from+real_ip_header X-Real-IP直接覆盖$remote_addr,这样 PHP 里读$_SERVER['REMOTE_ADDR']就是真的了
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











