不少人在本地开发时WebSocket一切正常,应用一部署到线上、前面加了Nginx就立刻出问题:要么握手直接返回400,要么连接建立后几十秒到几分钟就莫名断开。这不是WebSocket本身的问题,而是Nginx作为反向代理时,默认行为并不支持HTTP协议升级到WebSocket协议,需要额外的配置才能让长连接顺利透传。本文把Nginx代理WebSocket的几个关键配置点逐一讲清楚,并附上可直接使用的完整配置。

为什么Nginx代理WebSocket需要特殊的Upgrade头
要理解配置为什么长那样,得先明白WebSocket的握手原理。WebSocket并不是一个独立于HTTP的新协议端口,它的握手阶段借用了HTTP/1.1的Upgrade机制:客户端先发一个普通的HTTP GET请求,但带上Upgrade: websocket和Connection: Upgrade这两个请求头,服务端如果支持,就返回101状态码,表示协议切换成功,之后这条TCP连接就不再走HTTP逻辑,而是由WebSocket协议全双工通信。
问题就出在这里。Nginx作为反向代理时,默认会把客户端请求转发给后端,但转发时会重写或丢弃Upgrade和Connection这两个头。后端收不到升级请求,自然不会返回101,客户端等不到协议切换就报错,这就是最常见的“本地能用、加了Nginx就400”的原因。
解决办法就是通过proxy_set_header把这两个头显式传给后端。同时,Nginx默认的代理是短连接思维,对已建立的双工长连接需要用proxy_http_version 1.1明确使用HTTP/1.1,因为HTTP/1.0并不支持Upgrade机制。这三个指令缺一不可,是WebSocket代理配置的基石。
location块的标准配置写法
下面是一份生产环境可直接使用的配置示例,假设WebSocket服务跑在后端8000端口:
location /ws/ {
# 切换到HTTP/1.1,Upgrade机制只在这个版本可用
proxy_http_version 1.1;
# 传递协议升级所需的两个关键请求头
proxy_set_header Upgrade $http_upgrade;
proxy_set_header 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_set_header X-Forwarded-Proto $scheme;
# 后端WebSocket服务地址
proxy_pass http://127.0.0.1:8000;
}
注意一个细节:Upgrade头的值用了变量$http_upgrade而不是写死为websocket。这样写的好处是,当同一个location既可能收到普通HTTP请求又可能收到WebSocket升级请求时,Nginx能根据客户端实际发来的头动态透传,兼容性更好。
如果不想为WebSocket单独划分location,也可以把这段头配置合并到通用的location /里。但要小心Connection头写死成"upgrade"后,普通HTTP请求的keep-alive行为会受影响,所以更优雅的方案是用map指令按需设置,下一节详细说。
用map动态处理Connection头
更规范的写法是在http块或server块里定义一个map,根据客户端是否携带Upgrade头来决定Connection的值:
# 放在http块中
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
listen 80;
server_name example.ipipp.com;
location /ws/ {
proxy_pass http://127.0.0.1:8000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
# 长连接超时设置,见下一节说明
proxy_read_timeout 300s;
proxy_send_timeout 300s;
}
}
map的逻辑很直白:客户端发了Upgrade头,$http_upgrade非空,Connection就设为upgrade;普通请求时Upgrade头为空,Connection设为close,不影响常规HTTP代理。这种写法是官方文档推荐的方式,适合WebSocket和普通接口共用一个域名的场景。
需要提醒的是,proxy_set_header有个继承特性:如果location里写了任何一条proxy_set_header,就会覆盖上层server或http块中的全部proxy_set_header,而不是合并。所以头配置要么集中写在location里,要么全写在server里,避免部分生效部分被覆盖导致排查困难。
长连接断开:超时参数才是元凶
很多配置正确的同学依然遇到“连上一段时间就掉线”,这几乎都是Nginx的默认超时在作怪。proxy_read_timeout默认只有60秒,含义是如果60秒内后端没有任何数据发往客户端,Nginx就主动关闭连接。聊天室这种低频消息场景,两次消息间隔很容易超过60秒,于是连接被Nginx悄悄掐断,客户端表现为1006异常关闭。
解决思路有两条。第一条是简单粗暴地把proxy_read_timeout调大,比如设为3600s甚至更长,配合proxy_send_timeout一起调整。这种方式配置最少,但依赖业务层的消息频率,如果真的一小时没有任何数据流动,连接还是会被关。
第二条是更工程化的做法:在应用层实现心跳机制,客户端每25到30秒发一次ping帧,服务端回pong。这样既能让Nginx的重置计时器不断刷新,也能及时检测出半开连接(比如客户端断网后TCP并没断)。实际上各类WebSocket库的心跳功能,很大程度上就是为了对抗中间代理的超时策略,建议无论如何都加上。
wss、502等常见问题排查
线上使用HTTPS站点时,前端必须用wss://协议而不是ws://,否则浏览器会因混合内容策略直接拒绝连接。Nginx侧不需要额外配置协议转换,只要WebSocket的location落在配置了SSL证书的server块里,Nginx会自动完成TLS卸载,后端依旧接收明文的WebSocket流量。
如果握手阶段就返回502,先检查后端服务是否真的在监听对应端口,用ss -tlnp | grep 8000确认。如果后端用的是Node.js的某些框架,还要确认它绑定的是0.0.0.0而不是127.0.0.1,以及Nginx所在机器能否访问到后端。另外,用了负载均衡(upstream)时,务必在upstream里配置ip_hash或开启后端会话保持,否则握手和后续请求被分到不同后端机器,连接会随机失败。
最后提一个容易忽略的点:如果Nginx前面还有CDN或者另一层代理,要确认那一层也支持并透传Upgrade头,任何一环卡住都会导致升级失败。排查时可以在客户端用浏览器开发者工具看101状态码是否返回,逐步定位断在哪一层。
总结一下核心要点:Upgrade和Connection两个头必须传、proxy_http_version必须是1.1、proxy_read_timeout要按业务调大并配合心跳、HTTPS下记得用wss协议。把这四点配置到位,Nginx代理WebSocket就能长期稳定运行。
Nginx反向代理WebSocketproxy_read_timeout修改时间:2026-09-10 01:04:46