应使用ip2long转整型后按RFC标准位运算校验:先filter_var验证IPv4格式,再排除127.0.0.0/8、10.0.0.0/8、172.16.0.0/12、192.168.0.0/16、100.64.0.0/10及0.0.0.0和255.255.255.255。

PHP 获取本机 IP 时返回 127.0.0.1 或私有段,怎么判断它不合法?
PHP 中用 $_SERVER['SERVER_ADDR'] 或 gethostbyname(gethostname()) 拿到的“本机 IP”,很可能只是回环地址或内网地址(如 127.0.0.1、192.168.x.x、10.x.x.x、172.16.x.x–172.31.x.x),不能直接用于外网通信或日志标记。关键不是“获取失败”,而是“获取到了但不可用”。
校验逻辑必须分两步:先确认是否为 IPv4 地址格式,再排除所有 RFC 1918 / RFC 5735 定义的保留段。
-
filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)是基础门槛,过滤掉空值、IPv6、格式错误 - 接着用
ip2long($ip)转整型后做位运算比字符串匹配更可靠(避免strpos误判192.168.10.1匹配到192.168就截断) - 特别注意
0.0.0.0和255.255.255.255也属于非法主机地址,需单独排除
用 ip2long 做私有网段精确拦截(推荐写法)
字符串前缀匹配易出错,比如 stripos($ip, '10.') === 0 会漏掉 10.0.0.1 以外的变体,也挡不住 100.64.0.0/10(CGNAT 段)。用整型比较最稳:
function isValidPublicIp($ip) {
if (!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
return false;
}
$ipLong = ip2long($ip);
if ($ipLong === false || $ipLong === -1) return false;
// 127.0.0.0/8
if (($ipLong & 0xFF000000) === 0x7F000000) return false;
// 10.0.0.0/8
if (($ipLong & 0xFF000000) === 0x0A000000) return false;
// 172.16.0.0/12
if (($ipLong & 0xFFF00000) === 0xAC100000) return false;
// 192.168.0.0/16
if (($ipLong & 0xFFFF0000) === 0xC0A80000) return false;
// 100.64.0.0/10(CGNAT)
if (($ipLong & 0xFFC00000) === 0x64400000) return false;
// 0.0.0.0/8 和 255.255.255.255
if ($ipLong === 0 || $ipLong === 0xFFFFFFFF) return false;
return true;
}
$_SERVER['SERVER_ADDR'] 为什么经常不准?
这个值取决于 Web 服务器(Apache/Nginx)绑定的监听地址,不是系统真实网卡 IP。常见陷阱:
立即学习“PHP免费学习笔记(深入)”;
- Nginx 反向代理后,PHP 看到的是 Nginx 所在机器的
127.0.0.1,不是源服务器外网 IP - Docker 容器里默认只有
172.17.0.x等桥接地址,SERVER_ADDR返回的是容器内网 IP - 多网卡服务器若 Apache 绑定在
192.168.1.100,就拿不到公网203.0.113.5 - 云厂商(如阿里云、腾讯云)的元数据服务返回的才是真实弹性公网 IP,需主动调用
curl http://100.100.100.200/latest/meta-data/public-ipv4
别信 gethostbyname(gethostname()) —— 它根本不可靠
该组合在多数 Linux 发行版上返回 127.0.0.1,因为 /etc/hosts 通常把 hostname 映射到本地回环。即使改 hosts,也无法覆盖多网卡、虚拟化、容器等场景。
真正需要本机公网 IP 的业务(比如注册 webhook 回调地址、生成签名用的源 IP),应该:
- 运维侧明确配置一个
APP_PUBLIC_IP环境变量 - 从云平台元数据接口拉取(注意超时和重试)
- 启动时用
ip route get 1.1.1.1 | awk '{print $7}'类命令查默认出口 IP(仅限有路由能力的环境)
IP 校验本身不难,难的是搞清你到底要哪个“本机”——是监听地址?出口地址?还是部署环境承诺的对外服务地址?没想清楚这点,代码写得再严谨也没用。











