websocket负载均衡需保证同一连接所有帧路由至同一后端,须透传upgrade头、启用会话保持、调大超时并禁用缓冲,且location需校验websocket握手合法性。

WebSocket 是长连接,不是每次请求都新建连接,所以 Nginx 做多实例负载均衡时,不能用普通 HTTP 的轮询方式。关键是要让**同一个 WebSocket 连接的所有帧始终打到同一台后端**,否则后端没有上下文,会直接断连(比如报错 1006 或 Unexpected response code: 200)。
必须透传协议升级头
WebSocket 握手依赖 HTTP/1.1 的 Upgrade 机制,Nginx 默认不转发关键头部,必须显式配置:
- proxy_http_version 1.1:强制使用 HTTP/1.1,这是 Upgrade 的前提
- proxy_set_header Upgrade $http_upgrade:变量名必须小写,$HTTP_UPGRADE 无效
- proxy_set_header Connection "upgrade":必须是字面量 "upgrade",不能写成 $http_connection(客户端可能是 keep-alive,会破坏握手)
更稳妥的做法是用 map 预处理 Connection 头:
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
再配合 proxy_set_header Connection $connection_upgrade。
必须做连接级会话保持
后端通常把用户状态(登录态、房间 ID、心跳计时器)存在本地内存里,连接一旦被轮询到不同节点,就丢失上下文。推荐三种绑定方式:
- ip_hash:最简方案,在 upstream 块开头加一行 ip_hash;。适合直连、IP 分散的内网环境;但遇到 CDN、NAT 或代理时,所有用户 IP 相同,会压垮单台机器
- sticky cookie:后端在握手成功后返回 Set-Cookie: ws_route=server-2,Nginx 配置 sticky cookie ws_route expires=1h;。兼容移动端、动态 IP 和代理环境,无需改后端逻辑
- hash $http_sec_websocket_key:利用每次握手唯一的 Sec-WebSocket-Key 做哈希。注意该值每次重连都变,只保证单次连接内路由一致,不跨连接复用
必须调大超时并禁用缓冲
Nginx 默认按短连接设计,对长连接极不友好。空闲超时或响应缓冲会导致静默断连:
- proxy_read_timeout 86400(24 小时):防止空闲期被主动关闭
- proxy_send_timeout 86400:同理,避免发送侧超时
- proxy_buffering off:禁用响应缓冲,避免帧堆积延迟
- proxy_http_version 1.1 和 Upgrade/Connection 头已配置的前提下,这些超时才真正生效
location 匹配要精确且带校验
建议在 location 块中加入握手合法性判断,避免非 WebSocket 请求误入:
location /ws/ {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
<h1>只允许 Upgrade 请求进入</h1><p>if ($http_upgrade != "websocket") {
return 403;
}
}</p>
这样可提前拦截非法请求,减少后端干扰。











