Nginx空闲断连WebSocket的主因是proxy_read_timeout过短(默认60秒)和Upgrade/Connection头未透传;需设proxy_read_timeout≥心跳间隔×2.5、proxy_buffering off、proxy_set_header Upgrade $http_upgrade与Connection "upgrade",并启用proxy_socket_keepalive。

Go服务本身没断,是Nginx在空闲时单方面关闭了连接。 根本原因就两个:proxy_read_timeout默认60秒太短,以及Upgrade/Connection头没透传导致握手失败或后续帧被误判。其他配置如keepalive、buffering、TCP参数不配齐,会放大问题但不是主因。
proxy_read_timeout设太小直接杀掉静默连接
这个值控制Nginx从Go后端读数据的等待上限,WebSocket握手完成后没有“响应体”,只有心跳或推送帧——只要60秒内没收到任何字节,Nginx就发FIN断开。Go服务甚至不知道连接已丢,客户端看到的是WebSocket is closed before the connection is established或upstream prematurely closed connection。
-
proxy_read_timeout必须显式设大,建议 ≥ 前端心跳间隔 × 2.5(例如前端每45秒ping一次,这里至少设120秒) - 不要设为0,Nginx不支持无限超时,会退化回默认60秒
- 同步检查
proxy_send_timeout,它控制Nginx向后端发数据的超时,也应设为相同量级
Upgrade和Connection头缺失导致握手失败
gorilla/websocket等库严格校验Upgrade: websocket和Connection: Upgrade这两个头。Nginx默认不转发它们,结果Go服务收到的是普通HTTP请求,Upgrader.Upgrade()直接返回400 Bad Request或panic,浏览器Network里可能显示200但实际没升到101。
- 必须在
location块中加这两行:proxy_set_header Upgrade $http_upgrade;和proxy_set_header Connection "upgrade"; - 注意第二行双引号不能少,否则Nginx会把
upgrade当变量解析成空字符串 - 漏掉任意一行,握手就失败;即使握手成功,后续某些代理路径也可能因头丢失导致复用连接异常
proxy_buffering on会破坏WebSocket帧边界
WebSocket靠精确的帧分隔(FIN bit、opcode、length)传递消息。如果Nginx开启proxy_buffering on(默认值),它会把Go服务连续发出的多个小帧攒进缓冲区,等满或超时才转发——客户端收到的是拼接乱序的二进制流,gorilla/websocket解析时直接报websocket: bad write message type或静默丢弃。
- 必须加
proxy_buffering off;禁用缓冲 - 同时确保
proxy_http_version 1.1;已启用,否则Nginx可能降级用HTTP/1.0处理,导致Connection: keep-alive失效 - 该配置只对当前
location生效,别写在http块里影响其他接口
proxy_socket_keepalive和系统TCP参数要协同生效
即使proxy_read_timeout调大了,中间网络设备(防火墙、云厂商SLB)仍可能因空闲超时静默断连,表现为偶发502/504或Connection reset by peer。这时仅靠应用层心跳不够,需底层TCP保活探测。
- 在
upstream块中启用proxy_socket_keepalive on;(Linux ≥ 3.9支持) - 配合
upstream里的keepalive 32;维持连接池,避免频繁建连 - 调整系统级TCP参数:
net.ipv4.tcp_keepalive_time = 300(5分钟开始探测),net.ipv4.tcp_keepalive_intvl = 60(间隔) - 注意:该机制只对Nginx→Go的连接生效,不影响客户端→Nginx链路
最易忽略的是proxy_buffering off和proxy_set_header Connection "upgrade"里那对双引号——前者让帧流原样透传,后者确保upgrade字符串不被Nginx变量引擎吃掉。这两个点一旦漏掉,其他所有超时调优都白搭。


















