ThinkPHP 中 request()->ip() 不准是因为默认不解析代理头,需配置可信代理段并结合 X-Real-IP、X-Forwarded-For 等头字段校验;Nginx 必须透传对应头信息,且三者(配置、定义、解析)必须严格匹配。

ThinkPHP 获取客户端真实 IP 不能直接用 $_SERVER['REMOTE_ADDR'],因为代理、CDN、负载均衡会覆盖它;必须结合 X-Forwarded-For、X-Real-IP 等头信息做可信链判断。
为什么 request()->ip() 有时不准
ThinkPHP 内置的 request()->ip() 默认只信任 REMOTE_ADDR,不解析代理头。当应用部署在 Nginx + CDN 或 SLB 后,它返回的是上一跳(如 CDN 节点)IP,而非用户真实 IP。
- 开启代理 IP 解析需手动配置
trust\_proxy参数 - ThinkPHP 5.1+ 支持设置可信代理网段,但默认为空,等同于不信任任何代理头
- 若 Nginx 没有透传
X-Forwarded-For或写法不规范(如重复追加),request()->ip()仍会失效
如何安全获取真实客户端 IP(推荐封装)
建议封装一个独立方法,显式控制头字段优先级和可信代理校验逻辑,避免依赖框架默认行为。
- 优先读取
X-Real-IP(Nginx 常用,较干净) - 其次解析
X-Forwarded-For的最左非私有 IP(注意:该字段可被伪造,必须配合可信代理校验) - 仅当请求来源 IP 属于你配置的可信代理段(如
10.0.0.0/8、192.168.0.0/16、CDN 回源段)时,才采信其携带的代理头 - 过滤掉局域网 IP 和无效地址(
127.0.0.1、::1、空值等)
示例封装函数:
立即学习“PHP免费学习笔记(深入)”;
function getClientIp()
{
$request = \think\Request::instance();
$ip = $request->server('remote_addr', '');
$xRealIp = $request->header('x-real-ip', '');
$xForwardedFor = $request->header('x-forwarded-for', '');
<pre class="brush:php;toolbar:false;">// 可信代理列表(根据实际部署填写,例如阿里云 SLB 回源段、公司内网段)
$trustedProxies = [
'10.0.0.0/8',
'192.168.0.0/16',
'172.16.0.0/12',
'127.0.0.1',
'::1',
];
$clientIp = '';
// 1. 如果 X-Real-IP 存在且来源可信,直接采用
if (!empty($xRealIp) && isTrustedProxy($ip, $trustedProxies)) {
$clientIp = $xRealIp;
}
// 2. 否则解析 X-Forwarded-For(取第一个非私有、非保留的 IP)
if (empty($clientIp) && !empty($xForwardedFor)) {
$ips = array_map('trim', explode(',', $xForwardedFor));
foreach ($ips as $candidate) {
if (isTrustedProxy($ip, $trustedProxies) && filter_var($candidate, FILTER_VALIDATE_IP) && !isPrivateIp($candidate)) {
$clientIp = $candidate;
break;
}
}
}
// 3. 最终 fallback 到 REMOTE_ADDR(仅当来源不可信时才用,通常不推荐)
if (empty($clientIp)) {
$clientIp = $ip;
}
return filter_var($clientIp, FILTER_VALIDATE_IP) ? $clientIp : '0.0.0.0';}
function isTrustedProxy($ip, $trustedProxies) { foreach ($trustedProxies as $proxy) { if (strpos($proxy, '/') !== false) { [$subnet, $mask] = explode('/', $proxy); if (ip_in_subnet($ip, $subnet, (int)$mask)) { return true; } } elseif ($ip === $proxy) { return true; } } return false; }
function isPrivateIp($ip) { $ipLong = ip2long($ip); if ($ipLong === false) return true; return $ipLong >= 0 && $ipLong = 167772160 && $ipLong = 2886729728 && $ipLong = 3232235520 && $ipLong
function ip_in_subnet($ip, $subnet, $mask) { $ipLong = ip2long($ip); $subnetLong = ip2long($subnet); $maskLong = -1
Nginx 配置必须同步透传头信息
光靠 PHP 封装不够,Nginx 必须正确设置代理头,否则后端根本收不到原始信息。
- 确保
fastcgi_param HTTP_X_REAL_IP $remote_addr;或更常见的是在location块中添加:proxy_set_header X-Real-IP $remote_addr; - 若经多层代理,用
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;(不是$remote_addr) - 禁止前端或中间层随意重写这些头,否则会导致 IP 被污染
- CDN 厂商(如 Cloudflare、阿里云 CDN)通常提供「回源请求头」开关,务必开启并确认传递了
X-Forwarded-For或Cf-Connecting-IP
真实环境中,IP 获取的可靠性取决于「Nginx 配置 + 可信代理段定义 + 头字段解析顺序」三者严格匹配;漏掉任一环,都可能拿到错误 IP。尤其是混合云架构下,SLB、API 网关、WAF 各自插入不同头字段,必须按实际流量路径逐层验证。











