1006是客户端在tcp无声断开且未收到close帧时自动生成的兜底状态码,非服务端发送;常见于nginx默认proxy_read_timeout=60秒早于心跳间隔导致rst、node.js http.server未禁用超时、移动端切后台回收连接等场景,需统一调大各层超时并校验upgrade头与连接状态。

1006 不是服务端发的错误码,是客户端在 TCP 连接无声断开、没收到任何 close 帧时自动生成的兜底状态。查服务端日志找不到它,重连逻辑不检查 event.wasClean === false 就等于没防住。
为什么 Nginx 代理后必现 60 秒 1006
Nginx 默认 proxy_read_timeout 是 60 秒,而多数前端心跳设为 30 秒——第二个 pong 还没回来,Nginx 已经发了 RST。这不是 WebSocket 协议问题,是代理层提前掐断了连接。
- 必须同时设置
proxy_read_timeout和proxy_send_timeout,建议 ≥ 300(5 分钟),且要大于心跳间隔 × 2 -
proxy_http_version 1.1缺失会导致降级到 HTTP/1.0,无法维持长连接 -
proxy_set_header Upgrade $http_upgrade和proxy_set_header Connection "upgrade"少一个,握手就失败;注意引号不能漏,$upgrade是错的 - 用 AWS ALB?它的空闲超时固定 60 秒不可调,得换 NLB 或把心跳压到 ≤ 25 秒
Node.js + ws 库里那个隐藏的 60 秒 timeout
用 noServer: true 模式调 handleUpgrade(),却不监听 server.on('upgrade'),等于把 socket 交给一个还在执行 HTTP 超时逻辑的 http.Server 管理。它会在 60 秒后直接 destroy socket,WebSocket 层完全感知不到。
WebSocket 8.18.2 是该协议规范的一个重要迭代版本,主要优化了连接稳定性与数据传输效率。它通过全双工通信机制,允许客户端与服务器在单一长连接上实时交换数据,大幅降低传统 HTTP 轮询的开销。该版本增强了心跳保活、自动重连及二进制帧传输能力,适用于即时通讯、在线游戏及金融行情推送等低延迟场景,为开发者提供更可靠的实时网络交互基础。
- 推荐做法:
new WebSocket.Server({ server })—— 它会自动禁用server.timeout和server.headersTimeout - 若需手动控制(如多协议共存),必须显式写
server.on('upgrade', (req, socket, head) => { wss.handleUpgrade(...) }) - HTTPS 场景下,
server必须是https.Server实例,传裸net.Socket会触发同样的超时
客户端 onclose.code === 1006 时该立刻检查什么
看到 1006 别急着改业务逻辑,先确认是不是环境或链路问题。这个码不触发 onerror,所有保活和重连必须基于 onclose 的判断。
- 用 Chrome DevTools → Network → WS → Frames 查最后帧:如果停在
Ping无Pong,说明中间件没透传或服务端没响应 - iOS Safari 切后台通常 30 秒内回收连接;Android WebView 行为不一,需监听
visibilitychange主动close并清定时器 - 发消息前必须检查
ws.readyState === WebSocket.OPEN,否则弱网切换后第一次send()就崩出 1006 - 重连要用退避:
setTimeout(connect, Math.min(retryDelay, 30000)),避免高频建连被防火墙限流
真正难处理的不是 1006 本身,而是它背后那个“谁切断的、在哪儿切断的、为什么等不到 pong 就 RST”的链路盲区。每个环节(Nginx、Node.js server、iOS 后台策略、NAT 设备)都可能静默截断,且不留日志。排查时得一层层排除,而不是盯着代码找 bug。










