一、引言:为什么 $remote_addr 不再是"真实"的?
在经典的单机 Web 架构中,应用服务器可以直接通过 REMOTE_ADDR 获取到客户端的真实公网 IP。然而,在现代分布式架构中,我们的服务前面通常会部署 CDN、云负载均衡器(如 AWS ALB, 腾讯云 CLB)或 Nginx 反向代理集群。
在这种多层代理的环境下,当请求最终到达你的应用服务器时:
- TCP 连接的源 IP (
$remote_addr) 已经变成了上一级代理服务器的内网 IP。 - 客户端的真实 IP 就像被层层包裹的信件,如果不进行特殊处理,你的应用将永远无法知晓它。
💡 核心价值 :
准确获取客户端真实 IP,是实现访问控制、安全审计、地域化服务、个性化推荐等几乎所有业务功能的前提!
本文将带你彻底搞懂 Nginx 在不同场景下透传和获取真实 IP 的完整方案。
二、两大核心机制:Header 透传 vs. 内置变量替换
要解决这个问题,主要有两种思路:
思路一:Header 透传(最常用)
- 原理 :每一层代理服务器都将客户端的 IP 地址(或整个代理链路)写入 HTTP 请求头(如
X-Forwarded-For,X-Real-IP),并传递给后端。 - 优点:简单、通用,适用于任何支持读取 HTTP Header 的后端应用(Java, Python, Go, PHP 等)。
- 缺点:HTTP Header 可以被客户端伪造,存在安全风险。
思路二:内置变量替换(更安全)
- 原理 :利用 Nginx 的
ngx_http_realip_module模块,直接将$remote_addr变量的值替换为从可信 Header 中提取的真实 IP。 - 优点:对后端应用完全透明,无需修改代码;并且只信任来自特定 IP 段的 Header,安全性更高。
- 缺点 :需要 Nginx 编译时包含
http_realip_module(大多数发行版默认已包含)。
三、实战详解:Header 透传方案
这是最基础也是最广泛使用的方法。
1. 核心 Header 介绍
X-Forwarded-For(XFF)- 格式 :
X-Forwarded-For: client_ip, proxy1_ip, proxy2_ip - 作用 : 记录了从客户端到服务器之间经过的所有代理 IP 列表。最左边的是原始客户端 IP。
- 特点: 是一个标准的、可追加的列表。每一层代理都会将自己的 IP 追加到末尾。
- 格式 :
X-Real-IP- 格式 :
X-Real-IP: client_ip - 作用 : 通常只存放最原始的客户端 IP。
- 特点: 是一个简单的键值对,由第一层代理设置,后续代理通常不再修改。
- 格式 :
2. Nginx 配置示例
# 假设这是你的边缘 Nginx(直接面向公网)
server {
listen 80;
server_name example.com;
location / {
# 将客户端的真实IP(即当前的$remote_addr)放入X-Real-IP
proxy_set_header X-Real-IP $remote_addr;
# 处理X-Forwarded-For
# 如果请求中已有XFF,则追加当前代理IP;否则,创建一个新的XFF并填入客户端IP
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://backend_app;
}
}
$proxy_add_x_forwarded_for: 这是一个 Nginx 内置变量,它的值等于X-Forwarded-For请求头的值加上", "和$remote_addr。如果原请求没有X-Forwarded-For头,它的值就是$remote_addr。
3. 后端应用如何获取?
后端应用需要主动从 HTTP Header 中读取这些值。
- Python (Django/Flask):
request.META.get('HTTP_X_REAL_IP')或request.headers.get('X-Forwarded-For').split(',')[0] - Java (Spring Boot):
@RequestHeader("X-Real-IP") String realIp - Node.js (Express):
req.headers['x-real-ip']
⚠️ 安全警告 :由于 Header 可伪造,切勿 在边缘 Nginx 上直接信任客户端传来的
X-Forwarded-For。正确的做法是,只有你的内部可信代理才能设置或追加这个 Header。
四、高级方案:使用 real_ip 模块(推荐用于内部服务)
当你有多个 Nginx 层级,或者希望对后端应用完全透明时,real_ip 模块是更好的选择。
1. 工作原理
real_ip 模块允许你指定:
- 哪些上游 IP 是可信的(即你的 CDN 或 LB 的回源 IP)。
- 从哪个 Header 中提取真实 IP。
当请求来自可信 IP 时,Nginx 会用 Header 中的 IP 覆盖 $remote_addr 的值。这样,对于后端 Nginx 或应用来说,$remote_addr 就已经是真实 IP 了。
2. Nginx 配置示例
# 假设这是你的内部 Nginx(接收来自公网 Nginx 或云LB的流量)
http {
# 定义可信的代理IP段
# 例如,阿里云SLB的经典网络回源IP段
set_real_ip_from 100.64.0.0/10;
# 例如,你的公网Nginx集群的内网IP段
set_real_ip_from 192.168.1.0/24;
# 指定从哪个Header中获取真实IP
real_ip_header X-Forwarded-For;
# 或者
# real_ip_header X-Real-IP;
# 开启递归解析(针对X-Forwarded-For)
# 会从右向左扫描XFF列表,直到找到第一个不在可信IP列表中的IP作为真实IP
real_ip_recursive on;
server {
listen 8080;
location / {
# 此时,$remote_addr 已经是客户端的真实IP了!
# 你可以直接用它做日志记录、限流等操作
access_log /var/log/nginx/access.log main;
# 如果后面还有应用,也可以继续透传
proxy_set_header X-Real-IP $remote_addr;
proxy_pass http://app_server;
}
}
}
set_real_ip_from: 必须配置,用于声明信任的上游代理。real_ip_recursive on: 对于X-Forwarded-For至关重要。它能智能地从client, proxy1, proxy2这样的列表中,剔除掉所有可信的代理 IP,从而精准地找到最原始的客户端 IP。
3. 日志记录
配置好 real_ip 模块后,你的 Nginx access_log 中记录的 $remote_addr 就是真实 IP,无需任何额外处理。
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent"';
access_log /var/log/nginx/access.log main; # $remote_addr 即真实IP
五、结语
感谢您的阅读!如果你有任何疑问或想要分享的经验,请在评论区留言交流!