做实时推送、在线聊天、行情刷新、协同编辑这类功能,WebSocket 基本上是绕不开的选项。前段时间有个项目在测试环境一切正常,一上生产连接就隔几十秒断一次,客户端日志一直刷 stream disconnected before completion: websocket closed by server before res,最后查下来问题根本不在业务代码,而是 Nginx 反代的默认超时把长连接掐了。这类坑非常典型,很多人把 Nginx 当成普通 HTTP 反向代理配完就扔,结果 WebSocket 长连接一上生产就花式掉线。
这篇文章就专门聊 Nginx 下的 WebSocket 长连接及数据容量配置,包括协议升级的原理、长连接保活参数、大数据帧传输相关的容量限制,以及负载均衡场景下的会话保持。无论你是用 Spring Boot、Node.js 还是 Go 做后端,只要前面挂了 Nginx,这份配置和排查思路基本都能直接套用。
1. WebSocket 反向代理的基本原理
1.1 从 HTTP 到 WebSocket:一次协议升级的握手
要搞清楚 Nginx 为什么需要特殊配置,得先明白 WebSocket 不是独立走一个端口,而是复用 HTTP 的握手过程。客户端发一个普通的 HTTP GET 请求,带上 Upgrade: websocket 和 Connection: Upgrade 这两个头,问服务端“能不能切到 WebSocket 协议”。服务端同意后,返回 101 Switching Protocols,此后这条 TCP 连接不再按 HTTP 的“一问一答”来用,而是变成了一个全双工的、可以双向持续收发数据的通道。
类比一下就是:HTTP 是每次去窗口办事,办完就走;WebSocket 是先递申请,窗口同意后直接给你拉一根专线,这根线上双方可以随时说话,不用再排队。
在 Nginx 反代的场景里,客户端和 Nginx 之间是一条 TCP 连接,Nginx 和后端服务之间是另一条 TCP 连接。握手请求到达 Nginx 后,Nginx 需要把带有 Upgrade 的原始请求原样转发给后端,并且在后端返回 101 之后,把这两条连接“焊死”在一起,之后双向的数据都是纯透传。这个“焊死”的动作,就是 Nginx 对 WebSocket 长连接的核心处理逻辑。
1.2 为什么默认的反向代理配置连不上 WebSocket
Nginx 默认向后端转发请求时,使用的是 HTTP/1.0,并且不会自动传递 Upgrade 和 Connection 这两个头。HTTP/1.0 本身没有 Upgrade 机制,后端收到一个没有 Upgrade 头的普通请求,自然返回正常的 200 响应,客户端等不到 101,WebSocket 握手就失败了。
所以最基础的配置要做两件事。第一,把 Nginx 和后端之间的 HTTP 版本升到 1.1,使用 proxy_http_version 1.1;。第二,手动把升级相关的头传给后端:
nginx复制proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
第一行的意思是“客户端请求里如果带了 Upgrade 头,就原样透传给后端”。第二行的意思是“无论客户端原始请求里的 Connection 是什么,都强制改成 upgrade”。这两行配合起来,后端才能识别出这是一次 WebSocket 升级请求。
这里有个细节必须说清楚:Connection 头是逐跳头,HTTP/1.1 规定连接相关的头不应该被传递给下一跳,Nginx 默认会清理掉这些头。所以不能只写 proxy_set_header Connection $http_connection 了事,直接写死 "upgrade" 是目前最稳的写法。
1.3 反向代理模式下 WebSocket 连接的生命周期
握手成功后,一条 WebSocket 连接会经历从建立、维持到断开的完整生命周期。在 Nginx 反代模式下,这条生命周期里有几个容易被忽略的角色:
- 客户端与 Nginx 之间的 TCP 连接;
- Nginx 与后端服务之间的 TCP 连接;
- 应用层 WebSocket 的 Ping/Pong 心跳;
- 操作系统层面的 TCP KeepAlive;
- Nginx 自带的
proxy_read_timeout/proxy_send_timeout超时器。
很多人只关心第一层,认为只要客户端和服务端都还活着,连接就永远会在。但 Nginx 在中间做转发时,一旦在 proxy_read_timeout 配置的时间内没有从后端读到任何数据,就会主动关闭连接,客户端拿到的表现就是连接被服务端断开。反过来,长时间没往客户端写数据,proxy_send_timeout 也会起作用。
理解了这个生命周期,再回头看那些“连接频繁断开”的问题,心里就有数了:要么是超时设置太短,要么是链路里某一个角色没有发心跳,要么是 TCP 层被防火墙或操作系统回收了。后面几节我会逐个讲透。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心配置详解与逐项调优
2.1 基础代理配置:升级头与 HTTP 版本
先给一份可以跑通的最小配置,所有参数都在后面逐项解释:
nginx复制map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
listen 80;
server_name ws.example.com;
location /ws {
proxy_pass http://backend_servers;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
}
}
