nginx代理websocket返回502,主因是未正确配置协议升级与超时:必须设proxy_http_version 1.1、proxy_set_header upgrade $http_upgrade、proxy_set_header connection "upgrade",并调大proxy_read_timeout至86400秒以维持长连接。

WebSocket 代理返回 502,基本不是后端没启动,而是 Nginx 没认出这是个长连接,直接按普通 HTTP 超时关掉了。
proxy_http_version 和 Upgrade 头必须同时配对
WebSocket 建立连接时会发起一次 HTTP 协议升级(Upgrade: websocket),Nginx 必须明确支持并透传这个过程。只写 proxy_pass 不加协议升级配置,Nginx 默认用 HTTP/1.0 转发,后端收不到 Upgrade 头,拒绝升级,最终返回空响应 → 触发 502。
-
proxy_http_version 1.1是前提,HTTP/1.0 不支持 Upgrade 机制 -
proxy_set_header Upgrade $http_upgrade:把客户端带的Upgrade头原样传给后端 -
proxy_set_header Connection "upgrade":不是$http_connection,必须硬编码为"upgrade",否则 Nginx 会按默认行为关闭连接 - 漏掉任意一项,日志里常出现
upstream prematurely closed connection或直接 502
proxy_read_timeout 必须设得足够大
WebSocket 连接建立后长期空闲不发数据,Nginx 默认 proxy_read_timeout 60s,超时就断开与后端的连接,但后端其实还活着 —— 客户端再发帧时,Nginx 已无有效 upstream 连接,只能报 502。
系统化 Debug 与根因调查框架:通过调查‑分析‑假设‑验证四步追踪根本原因,只进行根因修复,适用于 Bug 调试、异常行为分析、服务报错排查,提供可验证的结论。
- 建议设为
proxy_read_timeout 86400(24 小时)或更高,只要后端能保活,Nginx 就不该主动断 - 不要依赖
proxy_send_timeout或proxy_connect_timeout来“修”这个问题,它们管的是握手阶段,不是长连维持 - 如果后端本身有心跳机制(如 ping/pong),可略低于心跳间隔,但至少留出 2–3 倍余量
location 匹配路径不能带尾部斜杠歧义
当 proxy_pass 后带斜杠,Nginx 会重写路径;不带则原样转发。WebSocket 的 Sec-WebSocket-Key 和路径强相关,错配会导致后端无法识别连接请求。
- 错误写法:
location /ws/ { proxy_pass http://backend/; }→ 把/ws/abc改成/abc转发,后端路由收不到/ws/前缀 - 正确写法:
location /ws/ { proxy_pass http://backend; }(结尾无斜杠),保证路径完整透传 - 若后端监听根路径(如
http://localhost:3000/),且前端也连/ws/,那必须让后端能处理该路径,或用rewrite显式调整,别靠proxy_pass斜杠隐式裁剪
keepalive 和 worker_connections 影响并发稳定性
大量 WebSocket 连接下,Nginx 默认的连接池和工作进程资源容易耗尽,表现为偶发 502,尤其在连接密集建立/断开时。
- upstream 需启用
keepalive:upstream backend { server 127.0.0.1:3000; keepalive 64; },避免频繁建连触发 TIME_WAIT - location 中补全 HTTP/1.1 连接控制:
proxy_http_version 1.1+proxy_set_header Connection ""(清空 Connection 头,防止 Nginx 自动加close) - 检查
worker_connections是否够用:每个 WebSocket 连接占 1 个连接槽位,1000 并发至少配worker_connections 2048,配合worker_processes auto - 忽略这点,
error.log可能出现accept() failed (24: Too many open files)或无声 502
真正卡住人的地方,往往不是某条配置写错了,而是几项联动失效:比如开了 keepalive 却忘了清 Connection 头,或者调大了 proxy_read_timeout 却没改 upstream 的 fail_timeout,导致节点被误判下线。调参前先看 error.log 里具体哪句报错,比盲目堆参数更有效。










