ip_hash失效的根本原因是$remote_addr被多层代理覆盖为最后一跳IP,导致哈希不一致;需透传X-Forwarded-For、用map提取真实IP、改用hash $client_real_ip consistent实现稳定路由。

多层代理架构下,ip_hash 失效的根本原因不是 Nginx 配置写错了,而是它默认基于 $remote_addr 做哈希——而这个值在经过 CDN、负载均衡器、网关等多层转发后,早已变成最后一跳代理的 IP(比如 10.0.1.100),不再是用户真实 IP。直接用它做一致性哈希,会导致同一用户被轮询打到不同后端节点,会话丢失、缓存失效、限流错乱。
确保上游代理透传 X-Forwarded-For 并补全首字段
这是所有后续操作的前提。如果最外层代理(如 Cloudflare、HAProxy、API 网关)没把真实 IP 放进 X-Forwarded-For,后面再怎么处理都白搭。
- Nginx 作为中间代理时,必须显式设置:
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
注意不是$remote_addr,因为$proxy_add_x_forwarded_for会在原有头基础上追加当前客户端 IP,避免覆盖已有的代理链。 - 若使用 HAProxy,开启
option forwardfor;若走 Proxy Protocol(推荐用于 HTTPS 四层代理场景),则需后端 Nginx 启用set_real_ip_from+real_ip_header proxy_protocol。 - Cloudflare 用户务必在源站 Nginx 中信任其 IP 段:
set_real_ip_from 173.245.48.0/20;<br>set_real_ip_from 103.21.244.0/22;<br>...(完整段见 Cloudflare 官方文档)<br>real_ip_header CF-Connecting-IP;
用 map 指令提取真实 IP 并绑定到自定义变量
Nginx 原生 ip_hash 不支持从请求头取值,但可通过 map 提前解析 X-Forwarded-For,提取最左侧有效 IP,再用于 upstream 的哈希逻辑。
- 在
http块中定义映射:
~^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}) $1;
default $remote_addr;
}
- 该规则匹配
X-Forwarded-For开头的第一个合法 IPv4 地址(即真实客户端 IP),若头为空或格式异常,则回落到$remote_addr(兜底安全)。 - 注意:不建议直接用
$http_x_forwarded_for全量字符串,因其可能被伪造且含多个逗号分隔 IP。
改用 hash 指令替代 ip_hash,引用自定义变量
在 upstream 块中放弃 ip_hash,改用更灵活的 hash 指令,并指定刚才生成的 $client_real_ip:
hash $client_real_ip consistent;
server 10.0.2.10:8080;
server 10.0.2.11:8080;
}
-
consistent参数启用一致性哈希,比普通哈希更抗节点增减抖动。 - 这样,无论请求经过几层代理,只要
X-Forwarded-For正确携带了真实 IP,哈希结果就稳定指向同一台后端。 - 若业务对安全性要求高(防 IP 伪造),可配合
geo或map过滤私有地址、保留地址及已知代理网段,将非法X-Forwarded-For视为不可信,强制 fallback 到$remote_addr。
验证与兜底:日志记录 + 健康检查联动
光配完不验证,等于没做。关键是要确认日志里能稳定看到真实 IP,且哈希行为符合预期。
- 在 access_log format 中加入:
log_format main '$client_real_ip - $remote_addr ...';
对比$client_real_ip和$remote_addr,确认前者稳定为公网用户 IP。 - 对 upstream server 添加健康检查:
server 10.0.2.10:8080 max_fails=3 fail_timeout=30s;
避免因某台后端异常导致哈希倾斜或请求堆积。 - 上线初期可临时开启 debug 日志:
error_log /var/log/nginx/debug.log debug;
观察http map执行过程和变量取值是否符合预期。

















