开启 proxy protocol 后,$proxy_protocol_addr 是客户端真实 ip 的直接来源,但需确保 listen 指令启用 proxy_protocol、避免 realip 模块误用,并在日志或审计中安全引用该变量,同时验证 proxy 头是否被正确解析。

开启 Proxy Protocol 后,$proxy_protocol_addr 就是客户端真实 IP 的直接来源,但要让它在审计日志、访问控制或业务逻辑中“正确可用”,关键不在变量本身,而在于 Nginx 是否真正解析了 PROXY 头,以及后续是否被其他模块(如 ngx_stream_realip_module)覆盖或误用。
确保 listen 指令启用 proxy_protocol
这是最基础也是最容易遗漏的一步。四层(stream{} 上下文)中必须显式声明:
-
listen 1883 proxy_protocol;—— 表示该端口接受并解析 PROXY 协议头部 - 若监听多个端口(如 80、443、1883),每个需透传真实 IP 的端口都得加
proxy_protocol - 不加该标记,Nginx 完全忽略连接开头的 PROXY 头,
$proxy_protocol_addr始终为空
避免 realip 模块覆盖 $proxy_protocol_addr 的语义
ngx_stream_realip_module 的作用是把 $remote_addr 替换为真实客户端 IP,但它不会动 $proxy_protocol_addr —— 这个变量始终代表原始 PROXY 头里写的 IP,不受 realip 影响。但审计时容易混淆:
- 错误做法:依赖
$remote_addr审计,却未配置set_real_ip_from或信任源不匹配 → 得到的是负载均衡器 IP - 正确做法:审计日志中明确使用
$proxy_protocol_addr,它天然可信,只要上游(如 HAProxy、AWS NLB、Cloudflare Spectrum)确实发送了合法 V1/V2 头 - 注意:
$proxy_protocol_addr在连接未携带 PROXY 头时为空字符串,建议日志中同时记录$remote_addr作 fallback 对比
在日志与审计逻辑中安全使用该变量
定义日志格式时,直接引用即可,无需转换:
log_format audit 'time=$time_iso8601 client=$proxy_protocol_addr:$proxy_protocol_port via=$remote_addr';- 如果用于 access_log 或自定义审计模块(如 Lua、JS),确保判断空值:
if ($proxy_protocol_addr = "") { return 400; }(拒绝无 PROXY 头的直连) - 不建议用
$proxy_protocol_addr做限流或黑白名单的唯一依据——应配合set_real_ip_from+real_ip_header proxy_protocol统一走$realip_remote_addr流程,保持行为一致
验证 PROXY 头是否被正确接收和解析
最简单的验证方式是抓包或查看 Nginx 错误日志:
- 用
tcpdump -i any port 1883 -w proxy.pcap抓包,用 Wireshark 打开,检查 TCP payload 开头是否为PROXY TCP4(V1)或 12 字节固定签名(V2) - Nginx error log 中出现
client sent invalid or too large PROXY header表示解析失败,常见于上游未严格按协议发送、或中间设备截断/修改了首段数据 - 临时加一条日志:
access_log /dev/stdout main if=$proxy_protocol_addr;,只记录带 PROXY 头的请求,快速确认是否生效











