nginx稳定代理websocket的关键是全链路适配其长连接本质:透传upgrade/connection头、启用http/1.1、设proxy_read_timeout和send_timeout为86400、关闭buffering/cache/gzip、启用tcp_nodelay,并精准路径匹配与会话保持。

要让 Nginx 稳定代理 WebSocket 并真正管好连接生命周期,关键不是堆参数,而是每一步都贴合它“长连接、全双工、靠心跳存活”的本质。默认配置是为短连接设计的,稍有偏差就会握手失败、静默断连或帧乱序。
透传升级头,握手一次成功
WebSocket 握手失败(返回 200 而非 101)几乎都出在这里。Nginx 默认丢弃 Upgrade 和 Connection 这两个逐跳头,后端根本收不到升级信号。
- proxy_http_version 1.1; 必须写死——HTTP/1.0 不支持 Upgrade,缺了这句会静默降级
- proxy_set_header Upgrade $http_upgrade; 用变量转发原始值(如 websocket、mqtt),别硬写 "websocket"
- 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 消息不可缓存,启用会导致连接直接失败
- tcp_nodelay on; 绕过 Nagle 算法,小帧(如光标移动、VAD 触发)立即送达
路径匹配与会话保持要精准可靠
配置再正确,如果请求没进对的 location,或轮询打到无状态后端,连接照样失效。WebSocket 连接状态通常存在内存中,漂移即丢失。
- 用明确路径匹配,例如 location /ws/ { ... },避免用 location / {} 兜底
- 集群部署下启用会话保持:ip_hash(简单有效)、sticky cookie(需 Plus 或模块),或后端共享状态(如 Redis 存 session)
- 确保 SSL/TLS 配置与路径严格一致,HTTPS 下 wss:// 请求若被 HTTP redirect 或证书不匹配,也会中断握手











