应优先使用可信代理设置的 X-Real-IP 或 Cloudflare 的 CF-Connecting-IP,再 fallback 到 X-Forwarded-For 中首个合法公网 IP;记录日志需包含 real_ip、remote_addr、X-Forwarded-For 原值、UA、URI、Referer 及时间戳,采用 JSON 行格式。

PHP 获取客户端真实 IP 并记录完整日志,不能只依赖 $_SERVER['REMOTE_ADDR'],它在代理、CDN、负载均衡后大概率是中间节点 IP,不是用户真实出口 IP。
如何从多个 HTTP 头中提取最可信的客户端 IP
真实 IP 通常藏在 X-Forwarded-For、X-Real-IP、X-Forwarded-Host 等头里,但这些头可被伪造,必须结合信任链判断:
-
X-Real-IP(Nginx 常用):若你控制反向代理且只有一层,且 Nginx 配置了proxy_set_header X-Real-IP $remote_addr;,则该值可信度最高 -
X-Forwarded-For:格式为"a.b.c.d, x.y.z.w",最左边是原始客户端 IP,但整串可能被追加恶意值;必须只取第一个非私有地址(如排除127.0.0.1、10.0.0.0/8、172.16.0.0/12、192.168.0.0/16、::1等) - 永远不信任来自客户端的
X-Forwarded-For或X-Real-IP,除非你明确知道上游代理 IP 是白名单内的可信节点(比如你自己的 Nginx 或 Cloudflare)
PHP 中安全获取真实 IP 的函数写法
不要用网上随手搜到的“万能 IP 函数”,它们常忽略信任边界。推荐按以下逻辑实现:
function getRealIp(): ?string
{
$ip = $_SERVER['REMOTE_ADDR'] ?? null;
// 只在你确认 Nginx / Apache 传了 X-Real-IP 且来源可信时才用
if (isset($_SERVER['HTTP_X_REAL_IP']) && filter_var($_SERVER['HTTP_X_REAL_IP'], FILTER_VALIDATE_IP)) {
$ip = $_SERVER['HTTP_X_REAL_IP'];
}
// 若启用了可信代理(如 Cloudflare),可检查 CF-Connecting-IP
if (isset($_SERVER['HTTP_CF_CONNECTING_IP']) && filter_var($_SERVER['HTTP_CF_CONNECTING_IP'], FILTER_VALIDATE_IP)) {
$ip = $_SERVER['HTTP_CF_CONNECTING_IP'];
}
// X-Forwarded-For:只取第一个合法公网 IP,跳过私有网段和无效值
if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$ips = array_map('trim', explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']));
foreach ($ips as $candidate) {
if (filter_var($candidate, FILTER_VALIDATE_IP) && !filter_var($candidate, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
$ip = $candidate;
break;
}
}
}
return $ip && filter_var($ip, FILTER_VALIDATE_IP) ? $ip : null;
}
注意:filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE) 会拒绝私有地址,但需配合 FILTER_FLAG_NO_RES_RANGE 排除保留地址(如 127.0.0.0/8、0.0.0.0)。
立即学习“PHP免费学习笔记(深入)”;
记录 IP 日志时必须包含的上下文字段
单记一个 IP 没意义。一次有效访问日志至少应含:
-
timestamp:使用date('c')或microtime(true),避免时区混乱 -
real_ip:调用上面函数得到的结果 -
remote_addr:原始$_SERVER['REMOTE_ADDR'],用于排查代理配置问题 -
http_x_forwarded_for:原始头内容(不解析),审计时可回溯伪造路径 -
user_agent和request_uri:辅助识别爬虫或异常请求 -
http_referer:判断流量来源(注意可能为空或伪造)
写入文件时建议用 JSON 行格式(每行一个 JSON 对象),方便后续用 jq 或日志系统解析,不要用 CSV(UA 或 Referer 含逗号会破坏结构)。
常见踩坑点:Cloudflare、阿里云 SLB、Nginx 配置错位
很多问题不是代码写的不对,而是基础设施没对齐:
- 用 Cloudflare 时,必须开启「Always Use HTTPS」并确保源站只接受来自 Cloudflare IP 段的请求,否则
CF-Connecting-IP头可被绕过 - 阿里云 SLB 默认不透传真实 IP,需在监听规则中开启「获取真实 IP」,且后端 PHP 才能读到
X-Forwarded-For - Nginx 若没配
set_real_ip_from+real_ip_header,即使 PHP 读到了X-Real-IP,也可能来自不可信来源 - 本地开发用
php -S时没有代理,$_SERVER['REMOTE_ADDR']就是真实 IP;一上生产环境就失效——这是最典型的环境差异陷阱
真实 IP 获取不是纯 PHP 问题,是整个请求链路的信任配置问题。日志里多记几个字段,比死磕“一行代码拿真实 IP”有用得多。











