1006是客户端在TCP连接无声中断且未收到close帧时自动生成的兜底状态码,非服务端发送;根本原因包括Nginx代理超时(默认60秒)、Node.js http.Server默认超时与ws库noServer模式冲突、移动端后台回收及NAT超时等,需从网络层、代理配置、心跳节奏和客户端状态四线并行排查。

1006 不是服务端发的错误码,而是客户端在 TCP 连接无声断开后“自己填的兜底状态”,查服务端日志永远看不到它——所有排查必须从网络层、代理配置、心跳节奏和客户端状态管理四条线并行推进。
为什么 Nginx 一配就报 1006?
Nginx 默认把 WebSocket 当普通 HTTP 处理,proxy_read_timeout 和 proxy_send_timeout 都是 60 秒,哪怕你心跳设为 30 秒,第二个 pong 还没回来,Nginx 就已发 RST 断连,客户端收不到任何帧,只能填 1006。
- 必须显式设置:
proxy_read_timeout 300、proxy_send_timeout 300(建议 ≥ 心跳间隔 × 3) - 必须透传升级头:
proxy_set_header Upgrade $http_upgrade和proxy_set_header Connection "upgrade" - 漏掉任意一项,连接会在 upgrade 阶段失败或静默中断,现象都是
disconnected (1006): no reason
Node.js ws 库 + http.Server 的隐藏超时陷阱
用 ws.Server({ noServer: true }) 并手动调 handleUpgrade(),却不监听 server.on('upgrade'),等于放任 Node.js http.Server 对该 socket 施加默认 timeout(60 秒)和 headersTimeout(也常为 60 秒),与 WebSocket 协议完全冲突。
WebSocket 8.18.2 是该协议规范的一个重要迭代版本,主要优化了连接稳定性与数据传输效率。它通过全双工通信机制,允许客户端与服务器在单一长连接上实时交换数据,大幅降低传统 HTTP 轮询的开销。该版本增强了心跳保活、自动重连及二进制帧传输能力,适用于即时通讯、在线游戏及金融行情推送等低延迟场景,为开发者提供更可靠的实时网络交互基础。
- 错误写法:
const server = createServer(); const wss = new WebSocket.Server({ noServer: true }); server.on('request', ...); wss.handleUpgrade(...);—— 缺少server.on('upgrade') - 正确做法二选一:
– 推荐:new WebSocket.Server({ server })(自动禁用超时)
– 或显式:server.on('upgrade', (req, socket, head) => wss.handleUpgrade(req, socket, head)) - HTTPS 场景下,
server必须是https.Server实例,不能传裸 socket
移动端和 NAT 环境下的“假在线”问题
iOS Safari 切后台 30 秒内会回收 WebSocket 连接;Android 某些 ROM 或运营商 NAT 超时可能低至 90 秒。这类断连不触发 onclose,直到下次发消息才暴露为 1006,前端若只依赖 onclose 重连,就会卡死。
- 发消息前必须检查:
if (ws.readyState !== WebSocket.OPEN) { reconnect(); return; } - 心跳间隔不能只看服务端配置,要 ≤ 最短预期超时(例如设为 45 秒,留出缓冲)
- 客户端主动
close()前,务必清空ping定时器、取消未完成请求,否则残留逻辑可能干扰重连状态判断
ClawdBot / OpenClaw 类工具的特殊根因
像 OpenClaw 或 ClawdBot 这类带设备配对机制的工具,1006 很可能根本不是网络问题,而是安全拦截:首次访问时未完成配对,网关拒绝授权,握手后立即静默断开。
- 执行:
openclaw devices list,若看到Status: pending,就是配对卡住 - 修复:
openclaw devices approve --all或指定 ID 批准 - 绕过代理直连
http://localhost:18789,若直连正常而域名访问报1006,100% 是反向代理头缺失或Origin校验失败
真正难缠的 1006 从来不在代码里,而在 Nginx 配置的第 3 行、iOS 后台的第 28 秒、运营商 NAT 的第 300 秒,以及你忘记清理的那个 setInterval。

















