Nginx代理Go WebSocket频繁断连主因是proxy_read_timeout默认60秒,需在精确匹配的location块中显式设为≥3600的正整数,并配置proxy_http_version 1.1、proxy_set_header Upgrade $http_upgrade、proxy_set_header Connection "upgrade"、proxy_buffering off和gzip off。

Go WebSocket 在 Nginx 后面频繁断开,90% 是 proxy_read_timeout 默认 60 秒导致的,不是 Go 代码或前端问题。
proxy_read_timeout 必须显式调大
Nginx 对已升级为 WebSocket 的连接,仍用 proxy_read_timeout 控制“从后端读数据”的空闲等待时间。默认 60 秒一到,它就主动关闭连接,不管前端是否还在发心跳、后端是否健康。Go 的 gorilla/websocket 或 gobwas/ws 都无法抵抗这个底层 TCP 断连。
- 必须在对应
location块中设置,不能只写在http或server级:例如proxy_read_timeout 86400;(24 小时) - 该值应 ≥ 前端心跳间隔 × 2,且建议不低于 3600(1 小时),避免被中间云负载均衡器二次截断
- 如果设为 0,Nginx 会退回到系统默认值(仍是 60 秒),所以必须给具体正整数
Upgrade 和 Connection 头没透传,握手直接失败
Go WebSocket 服务依赖客户端请求中的 Upgrade: websocket 和 Connection: Upgrade 头完成协议切换。Nginx 默认不转发这两个头,导致后端收到的是普通 HTTP 请求,返回 200 或 403,而不是 101 Switching Protocols。
- 必须加这三行,且顺序和写法要严格:
proxy_http_version 1.1;proxy_set_header Upgrade $http_upgrade;proxy_set_header Connection "upgrade"; -
$http_upgrade是 Nginx 内置变量,会自动取客户端原始请求头;写成"$http_upgrade"或$http_upgrade都可以,但不能漏掉美元符 - 如果用了
rewrite或proxy_redirect,可能覆盖请求头,务必检查最终发给 Go 后端的请求里是否真有这两个头
proxy_buffering 和 gzip 会破坏 WebSocket 帧流
WebSocket 数据以二进制帧(frame)为单位双向流动,不是 HTTP body。Nginx 若启用缓冲或压缩,会等“攒够”再发,或修改 payload,导致帧边界错乱、心跳丢失、甚至 EOF 错误。
- 必须关闭:
proxy_buffering off;—— 防止 Nginx 缓存后端推送的数据 - 必须禁用 gzip:
gzip off;或至少确保gzip_types不包含text/plain、application/json等 WebSocket 常见数据类型 - 可选但推荐:
proxy_send_timeout 86400;,与proxy_read_timeout一致,防止大消息发送中途被掐断
验证是否真生效,别信“能连上”
连接建立成功 ≠ 长连接稳定。很多配置看似写了,但因 location 匹配不到、HTTPS 配置缺失或日志级别太低,实际未生效。
- 浏览器 Network 面板里点开 WebSocket 连接,看响应状态码是不是
101 Switching Protocols;如果不是,说明握手阶段就失败了 - 用
curl -i -N -H "Connection: Upgrade" -H "Upgrade: websocket" http://your-domain/ws手动触发握手,观察响应头是否含Upgrade: websocket和Connection: upgrade - 查 Nginx error log:
tail -f /var/log/nginx/error.log | grep -i "upstream.*closed\|prematurely",只要出现这类日志,就代表配置未起作用
最易忽略的一点:这些配置必须放在精确匹配 WebSocket 路径的 location 块里(比如 location /ws/ { ... }),而不是泛匹配的 location / { ... }。否则 Nginx 可能用其他 location 的默认超时值覆盖你写的配置。


















