c.ClientIP()在多级代理下常返回内网地址,因Gin默认只信任直连客户端,未配置SetTrustedProxies时会fallback到RemoteAddr(即最后一跳代理内网IP),且不解析X-Forwarded-For;必须显式设置可信代理网段并配合Nginx real_ip_module还原真实IP。

为什么 Gin 的 c.ClientIP() 在多级代理下经常返回内网地址
因为 Gin 默认只信任直连客户端(即上一级 Nginx 的出口 IP),而没配置可信代理网段时,c.ClientIP() 会直接 fallback 到 c.Request.RemoteAddr——这在多层代理(如 CDN → WAF → Nginx LB → Gin)里永远是最后一跳的内网地址,比如 10.0.1.5:42231。
它不会自动解析 X-Forwarded-For,除非你显式告诉 Gin 哪些 IP 是可信代理,否则它把整条 XFF 链都当不可信输入丢弃。
- 必须在启动时调用
engine.SetTrustedProxies(),传入你实际使用的代理网段(不是0.0.0.0/0) - 常见可信段示例:
[]string{"10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "127.0.0.1/32"}(按你云厂商 CDN/WAF/Nginx LB 的回源 IP 段精确填写) - 若用的是 Cloudflare,还需额外加
"2405:b500::/32"等 IPv6 段,否则 IPv6 用户真实 IP 仍丢失 - 传空切片
[]string{}或不调用该方法,等同于关闭信任机制,c.ClientIP()就失效
如何让 c.ClientIP() 正确提取 X-Forwarded-For 最左侧真实 IP
Gin 的 c.ClientIP() 在启用可信代理后,逻辑是:从 X-Forwarded-For 头取逗号分隔的 IP 列表,从右往左剔除所有可信代理 IP,返回最左边那个不可信(即真实客户端)IP。
所以它依赖两个前提:XFF 头完整、且顺序正确(client, proxy1, proxy2)。中间任何一层覆盖或截断 XFF,就会出错。
- 确保上游每层都用
$proxy_add_x_forwarded_for,而不是$remote_addr—— 后者会丢掉整条链 - 检查是否启用了
underscores_in_headers on;(某些云 WAF 用小写头名,Nginx 默认丢弃含下划线的 header) - 不要在 Gin 中手动解析
c.Request.Header.Get("X-Forwarded-For")再 split,那绕过了可信校验,可能被伪造 - 验证方式:在中间件里打印
c.ClientIP()和原始头c.Request.Header.Get("X-Forwarded-For")对比,看是否一致
必须配合 Nginx 的 real_ip_module 配置才真正可靠
光靠 Gin 解析 XFF 不够。攻击者可伪造 X-Forwarded-For: 1.1.1.1, 2.2.2.2,Gin 若没结合 Nginx 的 real_ip_module 提前还原 $remote_addr,就等于把伪造 IP 当作可信链一环参与计算。
正确链路是:Nginx 先用 set_real_ip_from + real_ip_header X-Forwarded-For + real_ip_recursive on 把真实 IP 写进 $remote_addr,再透传给 Gin;Gin 只需信任这一跳(即 Nginx IP),就能安全使用 c.ClientIP()。
- Nginx server 块中必须有:
set_real_ip_from 10.0.0.0/8;(填你自己的代理网段) - 同时透传头:
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - Gin 中
SetTrustedProxies只需填 Nginx 自身 IP 段(比如["127.0.0.1/32"]),因为 Nginx 已完成“剥洋葱” - 这样既防伪造,又避免 Gin 重复解析 XFF 导致性能损耗
WebSocket 握手阶段的真实 IP 获取要单独处理
Gin 的 c.ClientIP() 在普通 HTTP 请求里有效,但 WebSocket 升级请求(GET /ws)的生命周期不同:它走的是 Upgrade 流程,中间件可能未完全执行,或框架未将 XFF 注入到 websocket.Conn 对象。
更稳妥的方式是在 WebSocket handler 里手动读取:
- 用
c.Request.Header.Get("X-Forwarded-For")获取原始字符串 - 按逗号分割,取第一个非私有 IP(过滤
127.0.0.1、10.0.0.0/8、172.16.0.0/12、192.168.0.0/16) - 别依赖
c.ClientIP(),它在此场景下可能为空或返回127.0.0.1 - 确保 Nginx location 块中已配
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;和proxy_http_version 1.1
多级代理下,真实 IP 丢失从来不是单点问题。Nginx 的 real_ip_module 还原、Gin 的可信网段声明、XFF 的逐级透传,三者缺一不可。最容易忽略的是:Nginx 没开 real_ip_recursive on,导致它把 WAF 的 IP 当作真实客户端;或者 Gin 传了错误的网段(比如写了 0.0.0.0/0),反而让整个信任链失效。


















