Nginx 默认 proxy_read_timeout 60 秒导致 WebSocket 断连,需设为 86400 并配置 Upgrade/Connection 头透传、proxy_buffering off 和 HTTP/1.1。

不是 Go 服务不稳定,是 Nginx 默认把 WebSocket 当成普通 HTTP 连接在 60 秒后主动掐断了——你看到的 close 1006 unexpected EOF 或浏览器控制台里“connection closed before established”,几乎全是这个原因。
proxy_read_timeout 60 秒是断连主因
Nginx 的 proxy_read_timeout 控制它从 Go 后端读数据的等待时间。WebSocket 握手完成后没有 HTTP 响应流,Nginx 就纯靠这个超时来判断“上游还活着吗”。默认 60 秒一到,没收到任何帧(包括 ping/pong),它就发 FIN 包关连接,Go 服务甚至不知道发生了什么。
- 设为
proxy_read_timeout 86400;(24 小时)最稳妥,比业务心跳逻辑更可靠 - 必须放在
location /ws/ { ... }块内,写在server或http级别无效 - 别设
0:Nginx 不支持无限超时,会退化回默认 60 秒 - 如果 Go 服务设置了
websocket.DefaultWriteWait = 10 * time.Second,proxy_send_timeout也得同步设大,否则大消息发一半就被截断
Upgrade 和 Connection 头没透传,握手直接失败
Go 的 gorilla/websocket 严格校验两个请求头:Upgrade: websocket 和 Connection: Upgrade。Nginx 默认不转发它们,后端收不到升级信号,就当普通 HTTP 请求处理,返回 400 或静默关闭。
- 必须加这两行,且只在 WebSocket 对应的
location块里:proxy_set_header Upgrade $http_upgrade;proxy_set_header Connection "upgrade"; - 注意双引号:不加引号,
Connection upgrade会被 Nginx 解析成变量$connection,结果传过去的是空值 - 不能硬写
Connection "upgrade"给所有请求——非 WebSocket 请求(比如 AJAX)会被错误升级。更健壮的做法是在http块里配map:map $http_upgrade $connection_upgrade { default upgrade; '' close; }然后用
proxy_set_header Connection $connection_upgrade;
proxy_buffering on 会让推送消息乱序或丢帧
WebSocket 是流式协议,帧边界必须精确。Nginx 默认开启 proxy_buffering on,会把 Go 服务发来的多个小帧攒在一起再转发,破坏帧结构。客户端用 gorilla/websocket 解析时直接报 websocket: bad write message type 或静默丢弃。
- 必须显式关闭:
proxy_buffering off; - 同时确认
proxy_http_version 1.1;已启用——HTTP/1.0 不支持 Upgrade,且无法维持长连接 - 如果用了 HTTPS 反代,确保
server块里有完整 SSL 配置(证书、密钥、ssl_protocols TLSv1.2 TLSv1.3),否则浏览器拒绝建立wss://
最容易被忽略的是:Nginx 错误日志里出现 upstream prematurely closed connection 时,第一反应不该是查 Go 代码,而是立刻验证 proxy_read_timeout 是否生效、以及 $http_upgrade 变量是否为空——这两个点卡住,其他配置再全也没用。











