Nginx 真实 IP 透传方案

Nginx 真实 IP 透传方案(备选参考) 适用场景:单机部署,Nginx Stream 作为唯一对外入口,后端服务混合 HTTP / WebSocket / gRPC,需要将客户端真实 IP 透传给后端服务。 整体架构 外部客户端 ↓ Nginx Stream :443(唯一对外入口)

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_nameproxy_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_addrreal_ip_header 还原后的客户端真实 IP
$proxy_protocol_addrPROXY 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_nameproxy_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)。

LICENSED UNDER CC BY-NC-SA 4.0
评论