nginx稳定代理websocket负载均衡需协议透传、超时对齐、会话保持三者协同:必须显式设proxy_http_version 1.1,透传upgrade和connection头(推荐map动态处理),proxy_read_timeout等超时值须与后端心跳周期严格一致,启用ip_hash或sticky cookie防止连接漂移,并关闭buffering、gzip和cache。

要让 Nginx 在负载均衡场景下稳定代理 WebSocket 连接,关键不是“加功能”,而是确保协议升级信号完整透传、连接不被误断、会话不发生漂移。三者缺一不可。
必须正确透传协议升级头
WebSocket 握手依赖 HTTP/1.1 的 Upgrade 和 Connection 头,Nginx 默认会过滤或改写它们,导致后端收不到升级请求,返回 200 而非 101 状态码,前端直接报错 “Unexpected response code: 200” 或 “WebSocket is closed before the connection is established”。
- proxy_http_version 1.1 必须显式声明——HTTP/2 不支持 Upgrade,不能替代
-
proxy_set_header Upgrade $http_upgrade 中变量名必须小写,写成
$HTTP_UPGRADE会失效 -
proxy_set_header Connection $connection_upgrade 推荐配合 map 指令使用,比硬写
"upgrade"更健壮:
default upgrade;
'' close;
}
这样可避免客户端未带 Upgrade 头时,Nginx 错误地发送 Connection: upgrade 给后端,引发协议错误。
超时设置要分层且与后端对齐
Nginx 默认 proxy_read_timeout=60,只要 60 秒没从后端收到数据(比如心跳或业务消息),就会主动关闭连接,这是线上 1006 断连最常见原因。
- proxy_read_timeout 建议设为 300(5 分钟)起步;若支持弱网观战或离线重连,可设为 1800(30 分钟)
- proxy_send_timeout 同步设为相近值(如 300),防止广播消息推送卡住时被中断
-
keepalive_timeout 控制初始握手连接空闲期,建议略大于后端服务的连接超时(例如 Spring Boot 的
server.tomcat.connection-timeout=15s,此处设 20 即可)
所有超时值必须和后端框架(Netty/Swoole/Node.js)的读写超时、心跳检测周期严格一致,否则会出现“一端已关、另一端还在等”的状态错位。
强制会话保持,防止轮询漂移
WebSocket 连接建立后,用户状态(如游戏房间、聊天上下文、登录凭证)通常存在单个后端进程内存中。若负载均衡策略是默认轮询,一次心跳走 A 节点、下条消息到 B 节点,必然失败。
- 用 ip_hash:适合客户端 IP 稳定、无 NAT 场景(如内网调用)
- 用 sticky cookie(需 nginx-plus 或开源版搭配 lua-resty-session):更适用于公网用户,按 session ID 绑定
- 用 hash $http_sec_websocket_key:基于 WebSocket 握手 key 哈希,同一连接始终路由到同一节点(需注意 key 可能重复,仅作辅助)
不推荐纯轮询或 least_conn,除非后端已实现全量状态共享(如接入 Redis 存储会话)。
关闭干扰机制,适配实时通信特征
WebSocket 数据包小、频率高、顺序敏感,Nginx 默认行为可能破坏通信链路:
- 显式关闭缓冲:proxy_buffering off,避免小包积压延迟
- 禁用 gzip 压缩:gzip off 或在 location 块中覆盖全局配置,防止压缩引入额外延迟和分片风险
- 禁用缓存:proxy_cache off 且确保 proxy_cache_bypass $http_upgrade 已设,防止握手请求被缓存层拦截
这些不是“可选优化”,而是避免协议降级和连接异常的必要操作。











