获取客户端真实IP
约 745 字大约 2 分钟
2026-08-29
服务器前面有反向代理、CDN 或负载均衡时,Nginx 直接记录的 $remote_addr 是代理节点的 IP,不是访客的真实 IP——统计、风控、封禁都会失准。解决办法是让代理把真实 IP 放进请求头传给后端,后端再从该头中读取。
原理:X-Forwarded-For 与 X-Real-IP
- X-Forwarded-For(XFF):标准头,记录链路上每一跳 IP,多个以英文逗号分隔,第一个通常就是客户端真实 IP。
- X-Real-IP:非标准头,由最前面那层代理设置,直接填客户端 IP。
请求头可以伪造
XFF 由客户端也能发,直接取值可能拿到假 IP。后端只能信任自家的代理/CDN 传来的头,且安全判断(登录、风控)要以配置了可信代理的 realip 模块结果为准。
Nginx 后端:两种取法
方式一:改日志与变量取 XFF(简单)
修改日志格式,把 $remote_addr 换成从 XFF 解析的值:
http {
log_format main '$http_x_forwarded_for - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent';
access_log /var/log/nginx/access.log main;
}多个站点与日志的配置方法见Nginx服务配置多站点。
方式二:realip 模块(推荐,应用无感)
让 Nginx 用 ngx_http_realip_module 把可信代理传来的 XFF 还原成 $remote_addr 本身,应用、日志、deny 规则全都自动拿到真实 IP:
http {
# 填写自家代理/CDN 的 IP 段,只有这些来源的 XFF 才会被信任
set_real_ip_from 10.0.0.0/8; # 内网代理网段按实际填写
set_real_ip_from 173.245.48.0/20; # CDN 网段按服务商文档填写
real_ip_header X-Forwarded-For;
real_ip_recursive on;
}real_ip_recursive on 表示从右往左排除所有可信代理 IP,剩下的第一个即真实客户端 IP。
应用层获取
后端语言从请求头读取(取 XFF 的第一段):
PHP
function real_ip(): string {
if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
// XFF 可能含多段,取第一个
return trim(explode(',', $_SERVER['HTTP_X_FORWARDED_FOR'])[0]);
}
return $_SERVER['REMOTE_ADDR'];
}Node.js
function realIp(req) {
const xff = req.headers['x-forwarded-for'];
if (xff) return xff.split(',')[0].trim();
return req.socket.remoteAddress;
}Java(Servlet)
String xff = request.getHeader("X-Forwarded-For");
String ip = (xff != null && !xff.isEmpty())
? xff.split(",")[0].trim()
: request.getRemoteAddr();多层代理时怎么取
链路越长 XFF 越长:client, proxy1, proxy2。原则:
- 信任链明确:用 realip 模块配
set_real_ip_from逐层声明可信网段,最可靠。 - 粗暴取第一段:在没有伪装客户端的场景(普通网站统计)够用,但不适用于安全封禁。
- 取最后一段是错的——最后一段是最靠近服务器的代理,不是访客。
验证方法
- 配置完成后重载 Nginx:
sudo nginx -t && sudo systemctl reload nginx。 - 从外网访问一次站点,看访问日志第一列是否为自己的公网 IP(
curl ifconfig.me可先查本机 IP)。 - 应用里打印取得的 IP 与日志比对一致即生效。
HTTPS 站点的证书与反代链路配置参见Nginx部署SSL证书;PHP 环境的部署见部署LNMP环境。
