Nginx 反向代理 WebSocket 频繁断连:一次 400 与 1006 的定位过程

举报
yd_255634470 发表于 2026/11/11 08:12:52 2026/11/11
【摘要】 从 Upgrade 头透传、proxy_read_timeout 与心跳、proxy_buffering 三个细节,记录一次 WebSocket 接入 Nginx 后的 400 与 1006 排查过程,并给出上线验证清单。

背景

最近把一个内部消息推送服务从 HTTP 轮询改成了 WebSocket。服务在本地用 wscat 连一切正常,一挂到 Nginx 后面就出现两种现象:一部分握手直接返回 400,另一部分能连上,但十几秒后无故断开,客户端报 1006。折腾了一下午,问题都出在代理层的三个配置细节上,记录如下。

一、400 的根因:Upgrade 头没有被透传

Nginx 默认以 HTTP/1.0 向后端转发,并且不会自动带上 Connection 与 Upgrade 这两个逐跳(hop-by-hop)头。后端收到的是一个普通 GET 请求,自然拒绝升级,直接回 400。

正确的写法是三个条件同时成立:

location /ws/ {
    proxy_pass http://127.0.0.1:9000;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header Host $host;
}

proxy_http_version 必须显式写成 1.1;Upgrade 要透传客户端带来的值;Connection 必须改写成 upgrade。少任何一个,要么 400,要么握手成功后立刻被关闭。

二、连上就断:读超时与心跳

第二个现象更隐蔽。抓包能看到服务端每 60 秒发一次 ping,但 Nginx 恰好在 60 秒整断开连接。原因是 proxy_read_timeout 默认 60 秒,而它计算的是两次读操作之间的间隔——如果业务长时间没有下行消息,即使 TCP 连接还活着,也会被判定为超时。

两种处理方式:

  • 把 proxy_read_timeout 提高到 300 秒以上;
  • 更稳妥的是让应用层带心跳,间隔明显小于超时时间,例如 25 秒一次 ping/pong。

我选了第二种,同时把超时设成 120 秒兜底。心跳还有一个额外好处:能更早发现中间链路被 NAT 或负载均衡回收的情况,而不是等用户反馈消息收不到。

三、容易被忽略的 buffering

proxy_buffering 默认开启,Nginx 会先把后端响应攒进缓冲区再发给客户端。对 WebSocket 这种长连接、小包、低延迟的场景,缓冲会带来可感知的延迟,个别版本上还会让帧被合并,前端解析出错。

proxy_buffering off;
proxy_cache off;

这两项建议一起关掉。顺带一提,如果前面还有一层 CDN 或 SLB,也要确认它支持 Upgrade 并且没有开启压缩缓冲,否则同样会在中间层断掉。

四、上线后的验证清单

  • 用 curl 带上 Connection: Upgrade 与 Upgrade: websocket 请求一次,确认返回 101;
  • 观察 error.log 中是否还有 upstream prematurely closed connection;
  • 客户端记录 close code:1006 多为链路被断,1011 多为服务端异常;
  • 用 ss -tnp 看连接是否稳定处于 ESTAB,而不是反复重建。

小结

WebSocket 走 Nginx,本质是把一条长连接交给一个为短连接设计的代理。协议版本、逐跳头、超时与缓冲这三件事必须显式声明,不能依赖默认值。改完这三处之后,之前偶发的 400 和 1006 都没有再出现。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。