c.RemoteIP() 总是返回内网地址或::1,因其默认仅读取c.Request.RemoteAddr(即TCP末端代理IP),而非真实客户端IP;需Nginx透传X-Real-IP/X-Forwarded-For头,并在Iris中手动校验可信代理段后提取最左非私有IP。

为什么 c.RemoteIP() 总是返回内网地址或 ::1
因为 c.RemoteIP() 默认只读 c.Request.RemoteAddr,而这个值是 TCP 连接末端(比如 Nginx、SLB、CDN 节点)的 IP,不是用户真实出口 IP。Iris 不像 Gin 那样内置 X-Forwarded-For 解析逻辑,也不自动信任任何代理段——它压根不解析代理头,除非你手动干预。
必须配置 Nginx 透传头,且不能漏掉关键变量
仅 Iris 端改代码毫无意义,上游没传头,后端再聪明也无从得知。Nginx 的 location 块里必须有这三行:
proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme;
注意点:
-
$proxy_add_x_forwarded_for是 Nginx 内置变量,写成"$remote_addr"或漏掉$符号会导致头为空 - 如果用了 Cloudflare、阿里云 WAF 或 SLB,它们也会追加
X-Forwarded-For;此时 Nginx 必须是“最后一级可信代理”,否则可能把 WAF 的 IP 当作用户 IP - 不要在
upstream块里重复定义这些proxy_set_header,顺序错乱会覆盖头
在 Iris 中安全提取真实 IP 的推荐写法
不能直接取 X-Forwarded-For 的第一个值——它可能被恶意伪造。必须结合可信代理段校验。Iris 没有 SetTrustedProxies 这类方法,得自己写校验逻辑:
- 只接受来自已知可信代理(如
10.0.0.0/8、172.16.0.0/12、192.168.0.0/16、100.64.0.0/10)的请求才信任X-Forwarded-For - 用
c.Request.Header.Get("X-Forwarded-For")取值,按逗号分割,取最左非私有 IP(跳过127.0.0.1、::1、10.x.x.x等) - fallback 到
c.Request.Header.Get("X-Real-IP"),再 fallback 到c.RemoteIP().String()
示例片段(Go):
func getRealIP(c iris.Context) string {
ip := c.Request().Header.Get("X-Forwarded-For")
if ip != "" {
for _, s := range strings.Split(ip, ",") {
s = strings.TrimSpace(s)
if net.ParseIP(s) != nil && !isPrivateIP(s) {
return s
}
}
}
if ip = c.Request().Header.Get("X-Real-IP"); ip != "" {
if net.ParseIP(ip) != nil && !isPrivateIP(ip) {
return ip
}
}
return c.RemoteIP().String()
}
容易忽略的部署细节:K8s Service 和 ELB 的 SNAT 问题
即使头传对了、代码写了,仍可能拿到 10.0.x.x 或 100.x.x.x ——这不是 bug,是基础设施行为:
- K8s
ClusterIP或NodePort(未设externalTrafficPolicy: Local)会做 SNAT,原始客户端 IP 被丢弃 - 阿里云/腾讯云 ELB 七层监听默认不透传源 IP,需确认后端服务器能收到
X-Forwarded-For;四层监听则要开启“获取客户端 IP”功能(独享型默认开) - Cloudflare 免费版只提供
CF-Connecting-IP,企业版才支持True-Client-IP;别指望免费版的X-Forwarded-For可靠
真实 IP 获取从来不是单点代码问题,而是 Nginx 配置、Iris 校验、网络拓扑三者咬合的结果。少一环,拿到的就只是代理链上某一级的 IP。


















