Nginx做WebSocket负载均衡必须启用连接保持,否则轮询会导致同一连接被分发到不同后端而断连;推荐ip_hash、Cookie粘滞或自定义Header+hash三种方式,并需配合HTTP/1.1、Upgrade头透传、超时调大及禁用缓存等基础配置。

要让 Nginx 正确做 WebSocket 负载均衡,关键不是“开启某个功能”,而是确保连接从握手到长续全程不被中断、不被错分——核心在于连接保持(session stickiness)。默认轮询会把同一客户端的后续帧发到不同后端,导致状态丢失、认证失效甚至消息乱序。
为什么必须做连接保持
WebSocket 是单次握手、长期存活的 TCP 连接,所有后续数据帧都复用该连接。后端服务(如 Swoole、Netty、gowebsocket)通常将用户会话、心跳状态、内存 Session 存在本地,不跨节点共享。若 Nginx 把重连请求或 ping 帧路由到新节点,后端无法识别该连接,直接拒绝或丢弃消息。
- 轮询(round-robin)会导致连接“漂移”,首次连 A,第二次 ping 到 B,B 无上下文 → 断连
- IP 哈希在 CDN 或 NAT 后失效(所有用户 IP 相同)
- 没有连接保持时,哪怕配置了超时和头部,也只解决“断连”问题,不解决“错连”问题
推荐的三种连接保持方式
根据部署环境选择最稳妥的一种:
- ip_hash(简单场景首选):适用于客户端直连 Nginx、无前置代理的内网或可信公网环境。Nginx 对 client IP 做哈希,固定映射到某台后端。配置简洁,无需后端配合。
- Cookie 粘滞(生产推荐):后端在 WebSocket 握手成功后,通过 HTTP 响应头 Set-Cookie: ws_route=server-02; Path=/; HttpOnly; SameSite=Lax;Nginx 配置 sticky cookie ws_route expires=1h domain=.example.com path=/;,后续所有 /ws/ 请求自动带该 Cookie 并路由到对应节点。兼容 CDN、移动端、动态 IP 场景。
- 自定义 Header + hash 指令(高阶控制):后端在握手响应中注入唯一标识(如 X-WS-Node: node-a),Nginx 使用 map 提取并 hash:map $upstream_http_x_ws_node $backend_node { ~^(.+)$ $1; },再 proxy_pass http://backend_pool/$backend_node; 需配合 upstream 的 hash $backend_node;
配套必须启用的基础配置
连接保持只是路由逻辑,还需底层参数协同,否则仍会超时断开:
- 强制 HTTP/1.1:
proxy_http_version 1.1;(WebSocket 升级机制依赖) - 透传升级头:
proxy_set_header Upgrade $http_upgrade;和proxy_set_header Connection $connection_upgrade;(配合 map 预处理更健壮) - 延长空闲超时:
proxy_read_timeout 86400;与proxy_send_timeout 86400;(必须显式设置,不能依赖默认 60 秒) - 禁用缓存与缓冲:
proxy_buffering off;、proxy_cache off;(WebSocket 是流式二进制帧,缓存会破坏协议)
验证连接是否真正保持
上线前务必实测验证,避免“配置写了但没生效”:
- 用 wscat 或浏览器 console 连接,观察 Nginx access log 中 $upstream_addr 字段是否始终为同一 IP:PORT
- 在后端服务中打印连接 ID + 所在节点名,对比多次 ping/pong 是否始终落在同一进程
- 模拟客户端断网重连:等待 2 分钟后再发消息,确认是否回到原节点(而非轮询到新节点)


















