websocket在nginx后频繁断开的核心原因是心跳间隔与proxy_read_timeout未对齐,必须设心跳为25/45/55秒避开60秒倍数,proxy_read_timeout至少设为60秒并留抖动余量,且四条基础配置proxy_http_version 1.1、proxy_set_header upgrade $http_upgrade、proxy_set_header connection "upgrade"、proxy_buffering off缺一不可。

WebSocket 在 Nginx 后频繁断开,核心不是“心跳没加”,而是心跳与 Nginx 超时参数没对齐——必须让心跳帧真实穿过 Nginx,且间隔避开它的超时共振点,同时超时值留出足够缓冲。
心跳间隔要错开 60 秒倍数
Nginx 默认 proxy_read_timeout 是 60 秒,若心跳恰好每 60 秒发一次,极易触发周期性误判断连。这不是偶然,是确定性风险。
- 推荐设为 25 秒、45 秒或 55 秒——足够频繁,又完全避开 60 的整数倍
- 慎用 30 秒:若服务端响应 pong 耗时接近 1 秒,实际空窗可能达 31 秒,仍有踩线风险
- 高并发场景(如 B 站)常用 15 秒,平衡稳定性与资源开销
Nginx 超时参数必须大于心跳并留余量
proxy_read_timeout 不是心跳上限,而是“允许的最大无数据时间”。它必须严格大于心跳间隔,并预留网络抖动和处理延迟空间。
- 心跳设为 25 秒 → proxy_read_timeout 至少设为 60 秒,生产环境建议 300–86400 秒(5 分钟–24 小时)
- proxy_send_timeout 同样需 ≥ 心跳间隔,防止服务端发 pong 或大消息时被中途掐断
- 避免盲目设为 7 天(604800 秒):虽保连接不断,但阻碍异常连接释放,增加内存与文件描述符压力
确保心跳帧真实流经 Nginx 代理链路
有些实现把心跳做成服务端内部定时器+内存状态刷新,不走 WebSocket 通道——这对 Nginx 完全无效。心跳必须是真实的帧,经 TCP 传输,穿过 Nginx。
- 前端用 ws.send("ping") 或 ws.ping()(如浏览器支持)发送
- 服务端收到后必须立即回 pong 或业务确认帧,不能只更新内存状态
- 用 curl 模拟握手:
curl -i -N -H "Connection: Upgrade" -H "Upgrade: websocket" http://host/ws,观察响应头是否含 Upgrade: websocket 和 Connection: upgrade
基础代理配置不可省略
心跳再准,若 Nginx 没正确识别 WebSocket 协议,一切归零。以下四条必须同在 location 块内:
- proxy_http_version 1.1; —— 启用 HTTP/1.1,支撑 Upgrade 机制
- proxy_set_header Upgrade $http_upgrade; —— 透传客户端原始 Upgrade 头(不能硬写 "websocket")
- proxy_set_header Connection "upgrade"; —— 固定字符串,告诉 Nginx “别关,要升”
- proxy_buffering off; —— 强制禁用缓冲,否则帧会卡在 Nginx,导致粘包或假断连











