
PHP获取客户端真实IP地址完整指南:代理与CDN场景下的最佳实践
一、为什么需要获取客户端真实IP
在日常的Web开发中,获取客户端的IP地址是一个非常常见的需求。比如记录用户访问日志、限制访问频率、地理位置识别、防止恶意攻击等场景,都需要知道真正访问的用户来自哪个IP地址。
但是,现在的网络环境越来越复杂。很多网站都使用了CDN加速、反向代理或者负载均衡器。当用户请求到达服务器之前,中间经过了这些中间层,那么服务器直接看到的IP地址其实是这些中间层的IP,而不是用户的真实IP。比如你用阿里云CDN,服务器端拿到的REMOTE_ADDR可能是CDN节点的IP,这样你就无法知道用户是从哪里来的。
因此,我们需要一套可靠的方案,从各种可能的请求头中提取出真实的客户端IP,同时还要考虑安全性,因为有些头信息是可以被客户端伪造的。
二、PHP中与IP相关的核心变量
PHP通过$_SERVER超全局数组来获取HTTP请求的相关信息。其中与IP有关的变量主要有以下几个,它们各自有不同的含义和使用场景。
REMOTE_ADDR
这是最基础也是最可靠的变量。它表示直接与服务器建立TCP连接的客户端IP地址。如果没有经过任何代理,这个值就是用户的真实IP。但如果经过了CDN或反向代理,这个值就变成了最后一个代理服务器的IP。
举个例子,你的服务器前面有一台Nginx做反向代理,那么PHP看到的REMOTE_ADDR就是Nginx服务器的IP,而不是用户的IP。所以单纯依靠这个变量是不够的。
HTTP_X_FORWARDED_FOR
这个变量是由代理服务器在转发请求时添加的HTTP头,用来记录原始客户端的IP地址。它的格式通常是多个IP用逗号分隔,比如192.168.1.1, 10.0.0.1, 203.0.113.5。从左到右,第一个IP是最原始的客户端IP,后面依次是经过的每一级代理的IP。
需要注意的是,这个头信息可以被客户端伪造。比如攻击者可以在请求中手动加上X-Forwarded-For: 1.2.3.4,如果不加验证,服务器就会认为用户IP是1.2.3.4。因此,在使用时必须先确认代理服务器是否可信,并且要过滤掉内网IP和私有地址。
HTTP_CLIENT_IP
这个变量是某些代理软件(比如一些透明代理)使用的,但并不是标准HTTP头,使用并不广泛。它的优先级通常低于X-Forwarded-For,而且同样可以被伪造。
HTTP_X_REAL_IP
这是Nginx等反向代理常用的一个自定义头,通常只包含一个IP地址,就是用户的真实IP。相比X-Forwarded-For,它更简单直接,但也不是标准头,需要代理服务器主动设置。
在实际应用中,我们应该按照一定的优先级顺序检查这些变量,同时做好过滤和验证。
三、一个通用的获取真实IP函数
基于上面的原理,我们可以编写一个健壮的PHP函数来实现IP获取。下面这个函数是目前业界比较通用的写法,兼顾了多种场景和安全性。
function getClientIp() {
$ipSources = [
'HTTP_X_FORWARDED_FOR',
'HTTP_CLIENT_IP',
'HTTP_X_REAL_IP',
'REMOTE_ADDR'
];
foreach ($ipSources as $key) {
if (!empty($_SERVER[$key])) {
// 处理X-Forwarded-For可能包含多个IP的情况
$ips = explode(',', $_SERVER[$key]);
foreach ($ips as $ip) {
$ip = trim($ip);
// 验证是否为有效的公网IP,排除私有地址和保留地址
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
return $ip;
}
}
}
}
// 兜底返回REMOTE_ADDR,如果都没有则返回0.0.0.0
return isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '0.0.0.0';
}函数逻辑详解
- 优先级排序:我们先检查
HTTP_X_FORWARDED_FOR,因为它最常用且能反映多层代理。然后是HTTP_CLIENT_IP和HTTP_X_REAL_IP,最后才是REMOTE_ADDR作为保底。 - 处理多IP:
X-Forwarded-For可能包含一串IP,我们用逗号分割后逐个检查。从左到右遍历,找到第一个有效的公网IP就返回。为什么要找第一个?因为第一个是最原始的客户端IP,后面的都是代理IP。 - 严格过滤:使用
filter_var配合两个标志位:FILTER_FLAG_NO_PRIV_RANGE排除私有IP(如10.x.x.x、192.168.x.x、172.16-31.x.x),FILTER_FLAG_NO_RES_RANGE排除保留IP(如127.0.0.1、169.254.x.x等)。这样就确保了返回的一定是可路由的公网IP。 - 兜底处理:如果所有变量都不存在或者都被过滤掉了,最后返回
REMOTE_ADDR。即使REMOTE_ADDR也是内网IP(比如在内部网络环境中),至少有一个值可用。如果连REMOTE_ADDR都没有(理论上不可能),则返回0.0.0.0作为标识。
使用示例
假设用户通过CDN访问,请求头中包含:
X-Forwarded-For: 114.114.114.114, 10.0.0.1那么函数会先读取HTTP_X_FORWARDED_FOR,得到字符串114.114.114.114, 10.0.0.1。分割后第一个IP是114.114.114.114,它是一个公网IP,通过验证后直接返回。第二个IP是内网的,会被跳过。
如果用户没有经过代理,直接访问服务器,那么HTTP_X_FORWARDED_FOR为空,函数会检查下一个变量,最终走到REMOTE_ADDR,返回用户的真实IP。
四、代理与CDN场景下的服务器配置
仅仅靠PHP函数还不够,还需要在服务器层面正确配置代理行为,否则PHP可能永远收不到正确的头信息。
Nginx反向代理配置
如果你的PHP应用跑在Nginx后面,需要在Nginx配置中添加如下设置,将客户端的真实IP传递给后端:
location ~ \.php$ {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# ... 其他配置
}$remote_addr是Nginx直接连接的客户端IP(可能是CDN节点或用户),$proxy_add_x_forwarded_for会在原有的X-Forwarded-For后面追加当前客户端的IP。这样PHP就能收到完整的链路信息。
Apache mod_proxy配置
如果使用Apache作为反向代理,可以在VirtualHost中设置:
ProxyPreserveHost On
RequestHeader set X-Forwarded-For "%{REMOTE_ADDR}s"注意,Apache的配置相对复杂,需要启用mod_headers和mod_proxy模块。
CDN厂商的特殊处理
不同的CDN厂商可能会使用不同的头名称。例如Cloudflare使用CF-Connecting-IP,阿里云CDN使用Ali-CDN-Real-IP。如果你使用的是特定CDN,最好查阅其文档,看看是否需要额外处理。一般可以在PHP中增加对这些自定义头的检查,放在优先级列表的前面。
例如,对于Cloudflare,可以在函数中加入:
if (!empty($_SERVER['HTTP_CF_CONNECTING_IP'])) {
return $_SERVER['HTTP_CF_CONNECTING_IP'];
}五、安全注意事项与常见陷阱
防止IP伪造
如前所述,X-Forwarded-For等头信息很容易被伪造。如果攻击者在请求中手动添加了一个假的IP,而你的服务器又没有正确验证,那么记录的IP就是假的。解决方法是:
- 只在信任的代理环境下使用这些头信息。如果服务器直接暴露在公网,没有前置代理,那么应该只用
REMOTE_ADDR,忽略所有代理头。 - 在代理服务器(如Nginx)上清除或覆盖客户端传入的代理头,只允许代理自身添加。例如Nginx可以用
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;,这会覆盖客户端传来的同名头。
内网环境下的处理
如果你的应用运行在内网,比如企业内部系统,那么用户的IP很可能是内网地址。此时使用FILTER_FLAG_NO_PRIV_RANGE会导致所有IP都被过滤掉,函数会回退到REMOTE_ADDR。这种情况下,你可以根据业务需求决定是否要修改过滤条件,比如去掉私有地址过滤,或者单独处理内网场景。
IPv6兼容性
filter_var的FILTER_VALIDATE_IP默认支持IPv4和IPv6。上面使用的标志位同样适用于IPv6,会自动排除本地链路地址(fe80::)等。如果你的应用需要区分IPv4和IPv6,可以进一步定制。
性能考虑
这个函数执行很快,但如果在每次请求中都调用,也不会有明显性能问题。不过要注意,不要在循环中重复调用,一次获取后缓存即可。
六、总结
获取客户端真实IP看似简单,但在现代网络架构下却有很多坑。正确的做法是:
- 理解各个变量的含义和优先级。
- 编写健壮的过滤函数,优先从代理头中提取公网IP,并用
REMOTE_ADDR兜底。 - 在服务器层面正确配置代理,确保传递正确的头信息。
- 注意安全,防止IP伪造,只在受控环境下使用代理头。
按照上述方案实施,你就可以在各种复杂网络环境中稳定地获取到用户的真实IP,为后续的日志分析、安全防护等功能打下坚实基础。
PHP获取IP$_SERVERHTTP_X_FORWARDED_FORREMOTE_ADDRIP验证修改时间:2026-08-02 05:20:03