nginx 稳定代理 websocket 的关键是全链路适配其长连接本质:透传 upgrade/connection 头、设为 http/1.1、调大 read/send 超时、关闭 buffering/gzip/cache、精准路径匹配与会话保持。

要让 Nginx 稳定代理 WebSocket 并保障连接生命周期,关键不是“让它不断”,而是让每个环节都适配 WebSocket 的通信本质:它不是请求-响应式短连接,而是一条持续存活、双向流动的 TCP 通道。Nginx 默认行为恰恰是为 HTTP 设计的——会缓存、会超时、会清理逐跳头,这些都会无声杀死长连接。
透传协议升级头,握手不能被截断
WebSocket 握手本质是一次 HTTP 升级请求,依赖两个逐跳头:Upgrade 和 Connection。Nginx 默认丢弃它们,导致后端收不到升级信号,返回 200 而非 101,前端直接报错 “Unexpected response code: 200”。
- proxy_http_version 1.1; —— 必须启用,HTTP/1.0 不支持 Upgrade 机制
- proxy_set_header Upgrade $http_upgrade; —— 动态转发客户端原始值(如 websocket、mqtt),不硬编码
- proxy_set_header Connection $connection_upgrade; —— 推荐配合 map 指令使用,比写死 "upgrade" 更健壮(避免客户端带多个 Connection 值时出错)
超时设置必须覆盖真实空闲周期
Nginx 默认 proxy_read_timeout=60s,只要 60 秒没从后端收到数据,就主动断开连接。而 WebSocket 空闲期可能长达数分钟甚至小时(如后台监控、观战等待),这是断连最常见原因。
- proxy_read_timeout 86400; —— 设为 24 小时,覆盖任意心跳间隔(例如后端每 30 秒 ping 一次,该值至少设为 60+ 秒)
- proxy_send_timeout 86400; —— 同步调大,防止后端发送大消息或广播帧时被中断
- keepalive_timeout 20; —— 仅控制握手阶段初始连接空闲时间,建议略大于后端服务的 connection-timeout(如 Spring Boot 默认 15s)
关闭干扰流式传输的默认行为
WebSocket 数据以帧(frame)为单位实时双向流动,Nginx 默认启用的缓冲与压缩会合并、延迟甚至破坏帧边界,尤其在推送密集或小包高频场景下明显。
- proxy_buffering off; —— 必须在 location 块中关闭,防止 TEXT/BINARY 帧被缓存粘包
- gzip off; 或确保 gzip_types 不包含 WebSocket 相关 MIME 类型(如 text/event-stream)
- proxy_cache off; —— WebSocket 消息不可缓存,启用会导致连接直接失败
路径匹配与后端绑定要精准可靠
配置再正确,如果请求没进对的 location,或轮询打到无状态后端,连接照样失效。
- WebSocket 路径(如 /ws/ 或 /api/socket)用精确匹配(= /ws)或前缀匹配(^~ /ws/),避免 rewrite 改写路径或头
- 多实例部署时,用 ip_hash 或 sticky cookie 实现会话强绑定;若选无状态方案,后端需用 Redis 共享连接上下文
- upstream 块中启用 keepalive 32;,并配置 proxy_next_upstream error timeout http_500; 提升故障转移能力











