go服务收不到真实ip是因为nginx默认不透传,需配置proxy_set_header x-real-ip $remote_addr和x-forwarded-for $proxy_add_x_forwarded_for,并在go中优先读x-real-ip,再fallback解析x-forwarded-for首个非私有ip。

Go 服务收不到真实 IP,只看到 Nginx 的内网地址
这是因为 Nginx 默认不会把客户端原始 IP 带进请求头,Go 的 req.RemoteAddr 拿到的是 TCP 连接对端(即 Nginx)的地址,比如 192.168.1.10:54321。必须依赖 Nginx 主动透传,且 Go 代码要主动读取对应 header。
nginx.conf 里必须加这三行 proxy_set_header
仅靠 proxy_pass 不够,必须显式设置以下 header,缺一不可:
-
proxy_set_header X-Real-IP $remote_addr;—— 最简方案,单层代理时直接用这个 -
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;—— 多层代理时保留完整链路,$proxy_add_x_forwarded_for会自动追加$remote_addr到已有值后 -
proxy_set_header X-Forwarded-Proto $scheme;—— 虽不直接影响 IP,但影响https判断,避免 Go 里误判协议
注意:X-Forwarded-For 可被客户端伪造,所以不能无条件信任;X-Real-IP 是 Nginx 自己设的,更可控,推荐优先读它。
Go 里怎么安全地提取真实 IP
别直接用 req.RemoteAddr 或盲目取 X-Forwarded-For 的第一个 IP。正确做法是:
- 先检查
req.Header.Get("X-Real-IP"),非空且不是"unknown"或"127.0.0.1"就用它 - fallback 到
X-Forwarded-For:取其逗号分隔后的**第一个非私有 IP**(如10.0.0.5, 192.168.1.2→ 选10.0.0.5),跳过127.0.0.1、10.0.0.0/8、172.16.0.0/12、192.168.0.0/16等内网段 - 最后 fallback 到
req.RemoteAddr(仅限调试或完全不可信链路)
示例片段:
func getClientIP(req *http.Request) string {
ip := req.Header.Get("X-Real-IP")
if ip != "" && ip != "unknown" && !strings.HasPrefix(ip, "127.") {
return strings.Split(ip, ",")[0]
}
ip = req.Header.Get("X-Forwarded-For")
if ip != "" && ip != "unknown" {
for _, i := range strings.Split(ip, ",") {
i = strings.TrimSpace(i)
if !isPrivateIP(i) {
return i
}
}
}
return strings.Split(req.RemoteAddr, ":")[0]
}
为什么 set_real_ip_from 和 real_ip_header 在 Go 场景下通常不用
因为 ngx_http_realip_module 的作用是让 Nginx **内部替换 $remote_addr 变量值**,从而影响 log_format 或 limit_req 等模块行为。但 Go 应用收不到这个“替换后的 $remote_addr”,它只能看到 Nginx 发出的 HTTP 请求头。所以除非你在 Nginx 日志里查真实 IP,否则这套机制对 Go 后端没直接价值。重点还是 header 透传 + Go 侧解析逻辑。
真正容易被忽略的是:Nginx 配置改了必须 nginx -s reload,而 Go 服务如果自己做了 IP 缓存(比如用 net/http 的中间件提前解析并缓存 header),可能需要重启或清缓存才能生效。











