ip_hash失效本质是$remote_addr未还原为真实客户端ip。需验证real_ip_module是否生效、x-forwarded-for是否透传、upstream中ip_hash配置是否合规,以及后端是否误用xff头。

排查 ip_hash 失效,本质是查“Nginx 算哈希时用的到底是不是真实客户端 IP”。只要 $remote_addr 还是内网地址(比如 10.0.0.5),ip_hash 就一定失灵——不是配置错了,而是输入源错了。
确认 $remote_addr 是否已被还原为真实 IP
这是最关键的一步。ip_hash 只认 $remote_addr,不读 X-Forwarded-For 或其他头。
- 在 log_format 中加入 $remote_addr $realip_remote_addr,对比两者是否一致且为公网 IP
- 如果 $remote_addr 仍是内网段(如 10.x、172.16–31.x、192.168.x),说明 real_ip_module 没生效
- 检查是否漏了 set_real_ip_from ——必须明确列出所有上游可信代理的 CIDR 段,不能只写一个或留空
- 确认 real_ip_header X-Forwarded-For(或 Cloudflare 用 CF-Connecting-IP)与实际传入的头名完全一致,大小写敏感
- 启用 real_ip_recursive on,否则遇到多层不可信代理时会取错位置
验证 X-Forwarded-For 全链路是否完整透传
哪怕 Nginx 入口配置正确,上游任意一层清空或覆盖 XFF,$remote_addr 就无法还原。
- 用 curl -H "X-Forwarded-For: 1.2.3.4" 请求入口 Nginx,看日志中 $remote_addr 是否变成 1.2.3.4 ——能则说明头被接受,不能则上游没传或被拦截
- 检查每一级代理(CDN/WAF/SLB/Nginx LB)是否都用了 $proxy_add_x_forwarded_for,而非手动写死 $remote_addr
- 禁止任何中间环节做 proxy_set_header X-Forwarded-For $remote_addr,这会抹掉原始链路
- 用 tcpdump 或 access_log 打印原始请求头,确认 X-Forwarded-For 值是否含多个 IP(如 203.208.60.10, 104.154.20.5, 10.0.1.8),最左才是用户真实 IP
检查 upstream 中 ip_hash 配置本身是否合规
即使 IP 正确,格式或上下文错误也会让 ip_hash 静默失效。
- ip_hash 必须顶格写在 upstream 块第一行,缩进、换行、前面有注释都会导致忽略
- 不能和 hash $arg_xxx、least_conn 等指令混用,冲突时以最后出现的为准,ip_hash 被覆盖
- 后端节点列表不能动态变化:增删 server、临时加 down 标记,会触发全量哈希重映射,大量用户 session 漂移
- IPv4 地址只取前三段参与哈希,192.168.1.100 和 192.168.1.200 视为相同 IP —— NAT 环境下易集中打到单台,属设计限制,非故障
排除后端服务干扰(间接影响 ip_hash 效果)
后端若误读 X-Forwarded-For 做业务逻辑(如限流、风控),可能掩盖 ip_hash 已生效的事实。
- 后端应优先信任 Nginx 注入的 X-Real-IP 头(由 realip_module 校验后生成),而非自行解析 XFF 字符串
- 禁用 Spring Boot 的
server.forward-headers-strategy=framework,改用native或显式配置RemoteIpValve - PHP-FPM 中避免用 $_SERVER['HTTP_X_FORWARDED_FOR'],改用 $_SERVER['HTTP_X_REAL_IP'](需 Nginx proxy_set_header X-Real-IP $remote_addr)











