不能直接用 net/http 暴露服务到公网,因存在连接泛洪、Slowloris 攻击、TLS 卸载缺失、HTTP/2 支持不全等安全与功能缺陷;必须通过 Nginx 作为反向代理承担网络边界职责,并正确配置超时、Header 透传、WebSocket 支持及真实 IP 解析。

为什么不能直接用 net/http 暴露服务到公网
Go 自带的 http.Server 虽然轻量可靠,但生产环境直连公网会立刻暴露在连接泛洪、慢速攻击(如 Slowloris)、TLS 卸载缺失、HTTP/2 支持不完整等问题下。Nginx 不是“可选中间件”,而是事实上的网络边界守门员。
常见错误现象:connection reset by peer 突增、too many open files 报错、HTTPS 请求 400(缺少 Host 头或 TLS 握手失败)、静态资源 404(路径未重写)。
- 必须关闭
http.Server.ReadTimeout和WriteTimeout(由 Nginx 控制超时更合理) - Go 服务监听应限定为
127.0.0.1:8080,禁止0.0.0.0或公网 IP - Nginx 需启用
proxy_buffering off(若 Go 服务流式响应,如 SSE 或 chunked JSON)
location / 配置里最常漏掉的三行
多数人只写 proxy_pass http://127.0.0.1:8080;,结果导致重定向跳转错乱、WebSocket 断连、Header 丢失——根本原因是 Nginx 默认不透传关键字段。
-
proxy_set_header Host $host;:否则 Go 的r.Host变成127.0.0.1:8080,影响生成绝对 URL -
proxy_set_header X-Real-IP $remote_addr;:绕过 Nginx 日志里全是127.0.0.1的问题 -
proxy_set_header Upgrade $http_upgrade;+proxy_set_header Connection "upgrade";:WebSocket 必须项,缺一则握手 400
示例片段:
立即学习“go语言免费学习笔记(深入)”;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
Go 服务如何感知真实客户端 IP 和协议
Go 默认从 r.RemoteAddr 拿到的是 Nginx 的回环地址,且 r.TLS 为 nil(因为 TLS 在 Nginx 终止)。必须靠 Header 解析,且顺序不能错。
- 先检查
X-Forwarded-For最左非私有 IP(注意:该 Header 可伪造,仅限可信代理链) - 用
X-Forwarded-Proto判断是http还是https,决定是否生成 HTTPS 链接 - Nginx 需显式设置:
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;(自动追加,不覆盖) - Go 代码中别直接信任
r.Header.Get("X-Forwarded-For"),要拆分并过滤127.0.0.1、10.0.0.0/8等内网段
健康检查和零停机部署怎么配才不翻车
Nginx 的 upstream 健康检查默认只看 TCP 连通性,而 Go 服务可能已启动但 DB 未就绪,导致流量打过去直接 500。同时 reload 时若没等旧连接关闭,用户会收到 502 Bad Gateway。
- Go 服务暴露
/healthzHTTP 接口,返回200 OK且无 body,Nginx 用health_check interval=3 fails=2 passes=2配合 - reload 前执行
nginx -t && nginx -s reload,但关键在 Go 侧:用http.Server.Shutdown()等待活跃请求结束(建议 10s 超时) - 避免在 Nginx 中用
max_fails=1—— 短暂 GC STW 或日志刷盘延迟都可能触发误判下线
容易被忽略的是:Nginx worker 进程 reload 后,旧进程仍处理完已有连接,但新连接全由新 worker 接管。如果 Go 服务 shutdown 超时设太短(如 1s),大量请求会被强制切断。










