应使用 map + 正则提取 $http_x_forwarded_for 中首个 IPv4 地址作为 $client_ip,再用于 hash 指令;或启用 real_ip_module 并配置可信代理网段,使 $remote_addr 自动替换为真实客户端 IP。

当客户端经过多层 CDN 或代理(比如 Cloudflare → 自建 LB → Nginx 反向代理 → 后端服务)时,直接用 $remote_addr 做 ip_hash 会导致所有请求被哈希到同一个后端节点——因为 $remote_addr 此时只是最靠近 Nginx 的那层代理的 IP(如 LB 的内网地址),完全失去客户端维度的分散性。
为什么不能直接用 $remote_addr 或 $http_x_forwarded_for 做 hash
• $remote_addr 是 TCP 连接发起方的 IP,即上一跳代理的出口 IP,不是用户真实 IP
• $http_x_forwarded_for 是一个逗号分隔的字符串,格式类似:
203.124.56.78, 198.51.100.20, 172.17.10.125
其中最左边的 203.124.56.78 才是客户端真实 IP;中间是各层可信代理 IP;最右是离你最近的代理(可能不可信)
• 若直接对整个字符串做 hash,结果不稳定(长度、空格、顺序易变),且含不可信字段,无法用于负载均衡
用 map + 正则提取首个合法 IPv4 地址作为 hash key
在 http 块中定义一个映射变量,安全、高效地截取第一个真实 IP:
map $http_x_forwarded_for $client_ip {
"" $remote_addr;
~^(?P<ip>[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}) $ip;
default $remote_addr;
}
说明:
• 第一行处理 X-Forwarded-For 为空的情况,回退到 $remote_addr
• 第二行用正则捕获开头连续的 IPv4 地址(不校验段值范围,兼顾性能;生产中基本够用)
• 第三行兜底,防止正则未匹配时变量为空导致 hash 异常
• 该 $client_ip 可直接用于 upstream 的 ip_hash 或 hash 指令
配合 real_ip_module 更严谨(推荐用于高安全场景)
若你已启用 ngx_http_realip_module(需编译时带 --with-http_realip_module),可让 Nginx 主动“信任并替换”$remote_addr:
set_real_ip_from 172.17.10.125; # LB 内网 IP set_real_ip_from 198.51.100.0/24; # CDN 网段(如 Cloudflare) real_ip_header X-Forwarded-For; real_ip_recursive on;
此时 $remote_addr 会被自动更新为 X-Forwarded-For 中最左的非信任 IP(即客户端真实 IP),后续可直接用于 ip_hash,无需正则提取。但前提是:你明确知道所有上游代理的可信 IP 范围。
最终 upstream 配置示例(使用提取后的 $client_ip)
upstream backend {
hash $client_ip consistent; # 支持一致性哈希,更稳
server 10.0.0.10:8080;
server 10.0.0.11:8080;
}
注意:
• 不要用 ip_hash 指令本身(它只认 $remote_addr),改用通用 hash 指令
• 加 consistent 参数可减少后端增减时的会话漂移
• 若需兼容 IPv6,正则需扩展,但多数 CDN 仍以 IPv4 透传为主


















