Nginx代理WebSocket必须配置proxy_http_version 1.1、proxy_set_header Upgrade $http_upgrade和proxy_set_header Connection "upgrade"三行,否则后端无法识别升级请求,导致握手失败或连接被静默关闭。

WebSocket负载均衡不是简单套用HTTP轮询就能跑通的,核心矛盾在于:连接建立后必须始终路由到同一台后端,否则后续帧会丢失或被拒绝。Nginx能做,LVS也能做,但机制完全不同,选错方案会导致重连风暴、消息乱序甚至连接假死。
为什么 Nginx 的 proxy_pass 必须配 proxy_http_version 1.1 和两个 upgrade 头
WebSocket握手本质是 HTTP/1.1 的 Upgrade 请求,客户端发 Upgrade: websocket + Connection: Upgrade,后端靠这两个头判断是否升级。Nginx 默认用 HTTP/1.0 转发,不带这些头,后端直接当普通 HTTP 处理,返回 400 或静默关闭。
-
proxy_http_version 1.1强制启用长连接支持,否则Upgrade无效 -
proxy_set_header Upgrade $http_upgrade是透传原始请求头,不能硬写"websocket"—— 客户端可能没发这个值,$http_upgrade 为空时 Nginx 会自动忽略该头 -
proxy_set_header Connection "upgrade"必须用双引号包裹字符串,否则 Nginx 会尝试变量替换,出错时日志里出现invalid value "upgrade" in "proxy_set_header"
Nginx 的 ip_hash 在 NAT 环境下会集体扎堆
局域网用户、运营商级 NAT、CDN 回源,都可能导致大量客户端共享同一个公网 IP。这时 ip_hash 不是“会话保持”,而是“会话拥塞”——所有流量打到一台后端,其他节点空转。
- 实测某教育平台上线后,50% 连接集中在一台 8C16G 服务器上,CPU 持续 95%,而其余节点平均 CPU
- 若必须用 IP 绑定,建议改用
sticky cookie srv_id expires=1h path=/(需 nginx-plus 或nginx-sticky-module-ng),但要注意前端必须接受 Cookie,且 WSS 场景下跨域需配withCredentials: true - 更稳妥的做法是放弃强绑定,后端用 Redis 维护
user_id → server_addr映射,Nginx 用纯weight轮询,连接建立后由业务层二次路由
LVS 的 DR 模式对 WebSocket 长连接基本无效
LVS 工作在 TCP 层,不解析 HTTP 头,也不维护连接状态。它只做初始 SYN 分发,后续包靠 TCP 会话表维持。问题在于:Linux 内核 conntrack 表默认老化时间是 5 天,但 WebSocket 空闲连接若无 ping/pong,很多中间设备(防火墙、运营商网关)会在 2–3 分钟就切断连接,此时 LVS 仍往原后端转发,包被丢弃,客户端卡住无响应。
- DR 模式下,LVS 不修改 IP 包,后端需配置 VIP,且无法做健康检查(没应用层语义)
- TUN 模式同理,隧道封装不解决超时穿透问题
- 唯一可行的是 LVS+Keepalived 做高可用 VIP,但真实负载均衡仍得交由 Nginx 或 Envoy 等七层代理来完成 —— LVS 只负责“入口不单点”,不负责“连接不中断”
proxy_read_timeout 设成 7d 是把双刃剑
文档常写 proxy_read_timeout 604800(7 天),看似保连接,实则埋雷:Nginx worker 进程会为每个空闲连接长期占用内存和 fd,连接数上万时极易触发 worker_connections is not enough 或 OOM。
- 真实场景中,应按业务心跳周期设 timeout:比如后端每 30s 发一次 ping,则
proxy_read_timeout设为 45–60s 即可 - 务必配
proxy_send_timeout,否则后端因网络抖动未及时 ACK,Nginx 会无限等待发包,卡住整个 worker - 生产环境建议加
health_check interval=10s fails=3 passes=2,但注意:标准 Nginx 开源版 health_check 仅支持 HTTP 状态码,对 WebSocket 需配合后端暴露 /health 端点返回 200
真正难的不是配通,而是理解“连接路由一致性”背后的服务治理逻辑:Nginx 解决的是 TCP 层分发问题,而消息不丢、状态不裂、故障可切,得靠后端共享存储 + 客户端重连策略 + 网关层连接复用协同完成。漏掉任何一环,表面连接不断,实际体验已崩。


















