Nginx 真实 IP 透传方案(备选参考)
适用场景:单机部署,Nginx Stream 作为唯一对外入口,后端服务混合 HTTP / WebSocket / gRPC,需要将客户端真实 IP 透传给后端服务。
整体架构
外部客户端
↓
Nginx Stream :443(唯一对外入口)
├── ssl_preread 读 SNI,不解密
├── proxy_protocol on,携带客户端真实 IP
└── map 按域名分流
├── Xray Reality :44443 → acceptProxyProtocol: true
├── HTTP 服务 :8443 → listen ssl proxy_protocol
├── WS 服务 :8443 → listen ssl proxy_protocol
└── gRPC 服务 :8443 → listen http2 ssl proxy_protocol
内部流量
└── 直接打后端端口(如 :9001),绕过翻译层,不带 PROXY Protocol
核心约束
PROXY Protocol 两端必须同时配对。
Stream 开proxy_protocol on,后端 Nginx 就必须listen xxx proxy_protocol,缺一不可,否则报 400。
一、Stream 配置
stream {
map $ssl_preread_server_name $backend {
xray.example.com 127.0.0.1:44443; # Xray Reality,自行处理 TLS
service-a.example.com 127.0.0.1:8443;
service-b.example.com 127.0.0.1:8443;
service-c.example.com 127.0.0.1:8443;
default 127.0.0.1:8443;
}
server {
listen 443;
ssl_preread on; # 读 SNI 用于分流,不解密
proxy_pass $backend;
proxy_protocol on; # 统一携带客户端真实 IP
}
}
二、公共 Snippet 文件
/etc/nginx/snippets/realip.conf
set_real_ip_from 127.0.0.1; # 信任来自本机 Stream 的流量
real_ip_header proxy_protocol; # 从 PROXY Protocol 中还原真实 IP
real_ip_recursive on; # 递归剥离,防止 IP 伪造
/etc/nginx/snippets/headers.conf(走 Stream 的服务使用)
proxy_set_header X-Real-IP ""; # 先清空,防伪造
proxy_set_header X-Real-IP $remote_addr; # 写入真实 IP
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # 追加,保留链路
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Host $http_host;
proxy_set_header X-Forwarded-Port $proxy_protocol_server_port; # 客户端请求的原始端口
/etc/nginx/snippets/headers-direct.conf(内部直接访问的服务使用)
proxy_set_header X-Real-IP "";
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;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Host $http_host;
proxy_set_header X-Forwarded-Port $server_port; # 用 $server_port,无 PROXY Protocol
三、后端 Nginx 服务模板
所有经过 Stream 的服务,复制此模板,只需改 server_name 和 proxy_pass。
普通 HTTP 服务
server {
listen 8443 ssl proxy_protocol;
include snippets/realip.conf;
server_name service-a.example.com;
ssl_certificate /etc/nginx/certs/wildcard.example.com.crt;
ssl_certificate_key /etc/nginx/certs/wildcard.example.com.key;
location / {
proxy_pass http://127.0.0.1:9001;
include snippets/headers.conf;
}
}
WebSocket 服务
server {
listen 8443 ssl proxy_protocol;
include snippets/realip.conf;
server_name service-b.example.com;
ssl_certificate /etc/nginx/certs/wildcard.example.com.crt;
ssl_certificate_key /etc/nginx/certs/wildcard.example.com.key;
location / {
proxy_pass http://127.0.0.1:9002;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
include snippets/headers.conf;
}
}
gRPC 服务
server {
listen 8443 http2 ssl proxy_protocol;
include snippets/realip.conf;
server_name service-c.example.com;
ssl_certificate /etc/nginx/certs/wildcard.example.com.crt;
ssl_certificate_key /etc/nginx/certs/wildcard.example.com.key;
location / {
grpc_pass grpc://127.0.0.1:9003;
grpc_set_header X-Real-IP $remote_addr;
grpc_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
四、Xray Reality 配置
Xray 直接从 Stream 分流,自行处理 TLS,只需开启 acceptProxyProtocol。
{
"inbounds": [{
"listen": "127.0.0.1",
"port": 44443,
"protocol": "vless",
"streamSettings": {
"network": "tcp",
"security": "reality",
"tcpSettings": {
"acceptProxyProtocol": true
},
"realitySettings": {
"dest": "www.microsoft.com:443",
"serverNames": ["www.microsoft.com"],
"privateKey": "你的私钥",
"shortIds": [""]
}
}
}]
}
Xray 必须只监听
127.0.0.1,不对外暴露,只能通过 Stream 访问。
五、内部流量处理
内部服务调用不带 PROXY Protocol,直接打后端端口,绕过翻译层:
内部调用 → 直接访问 127.0.0.1:9001(后端端口)
需要传递客户端 IP → 手动在 Header 中设置 X-Real-IP
六、关键变量说明
| 变量 | 含义 |
|---|---|
$remote_addr | 经 real_ip_header 还原后的客户端真实 IP |
$proxy_protocol_addr | PROXY Protocol 携带的原始客户端 IP |
$proxy_protocol_server_port | 客户端请求的原始端口(如 443) |
$proxy_add_x_forwarded_for | 在已有 XFF 基础上追加当前 $remote_addr |
七、X-Forwarded-For 说明
| 写法 | 行为 | 适用场景 |
|---|---|---|
$remote_addr | 直接覆盖,丢弃上游链路 | 最外层入口,防伪造 |
$proxy_add_x_forwarded_for | 追加,保留完整链路 | 中间层,不破坏已有链路 |
本方案中:入口层(Stream 还原后)已保证 $remote_addr 可信,中间层统一用 $proxy_add_x_forwarded_for 追加,后端取 X-Forwarded-For 第一个值即为真实客户端 IP。
八、新增服务 Checklist
- Stream
map中加一行域名到端口的映射 - 后端 Nginx 复制模板,修改
server_name和proxy_pass -
listen必须包含ssl proxy_protocol - 必须
include snippets/realip.conf - 必须
include snippets/headers.conf(在location块内) - 内部调用走后端端口,不走 8443
九、为什么不用其他方案
| 方案 | 排除原因 |
|---|---|
| TOA 内核模块 | 容器环境无法加载内核模块 |
| Traefik | 适合未来多机分布式,现阶段引入成本高 |
| APISIX | 依赖 etcd,部署复杂,现阶段过度设计 |
| Nginx Lua 自动探测 | 复杂度高,维护困难 |
| 翻译层统一处理 | 现有服务改动量大,风险高 |
当前单机、服务数量固定,PROXY Protocol + 模板化配置是最务实的方案。待迁移到多机分布式时,再统一引入 API Gateway(APISIX 或 Traefik Enterprise)。