在使用 Nginx 反向代理聊天系统、在线终端、实时通知、协同编辑、游戏后台或者其他 Web 应用时,经常会遇到一种情况:
普通网页可以正常打开,但 WebSocket 功能无法连接。
浏览器控制台可能出现:
WebSocket connection failed
Nginx 访问日志可能返回:
400
502
或者返回普通的:
200
但始终没有完成 WebSocket 协议升级。
正常的 WebSocket 握手成功后,服务端通常会返回:
101 Switching Protocols
因此排查时需要重点确认:
- WebSocket 上游服务是否运行
- Nginx 是否转发 Upgrade 和 Connection 请求头
- 代理路径是否正确
- HTTP 与 HTTPS 协议是否匹配
- Docker 网络和服务名是否正确
- 长连接是否因为超时被关闭
1. WebSocket 和普通 HTTP 有什么区别?
普通 HTTP 请求通常是:
客户端发送请求;
服务器返回响应;
一次请求完成后连接结束或复用。
WebSocket 则会先通过 HTTP 发起握手,请求将连接升级成 WebSocket。
客户端会发送类似请求头:
Upgrade: websocket
Connection: Upgrade
如果服务端接受升级,会返回:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
完成以后,客户端和服务端就可以在同一个连接中持续双向通信。
因此,WebSocket 反向代理不能只配置普通的:
proxy_pass
还需要正确处理协议升级请求头。
2. 常见的 WebSocket 故障表现
WebSocket 反向代理失败时,常见表现包括:
- 网页能打开,但实时消息不更新
- 在线终端一直显示连接中
- 浏览器控制台提示 WebSocket failed
- 握手返回 400 Bad Request
- Nginx 返回 502 Bad Gateway
- 握手返回 200,而不是 101
- 连接建立后约一分钟自动断开
- HTTPS 页面中 WebSocket 被浏览器拦截
- Docker 容器内正常,经过 Nginx 后失败
不同状态码对应的排查方向不同。
3. 先确认上游 WebSocket 服务是否运行
在修改 Nginx 之前,先确认后端程序本身能够正常运行。
假设 WebSocket 服务监听:
127.0.0.1:3000
先检查端口:
ss -lntp | grep 3000
如果没有任何输出,说明后端服务没有监听该端口。
继续检查后端程序、systemd 服务或者 Docker 容器状态。
如果是 Docker:
docker ps -a
如果是 Docker Compose:
docker compose ps
如果容器显示:
Exited
或者:
Restarting
应先解决容器启动问题,而不是继续修改 Nginx。
4. 直接测试上游 HTTP 接口
如果上游程序同时提供普通 HTTP 接口,可以先测试:
curl -I http://127.0.0.1:3000
或者测试健康检查接口:
curl -I http://127.0.0.1:3000/health
如果直接访问上游都提示:
Connection refused
说明后端没有正常监听。
如果提示:
Connection timed out
则需要继续检查网络、容器、监听地址或者防火墙。
只有上游服务本身正常以后,再继续检查 WebSocket 代理。
5. 测试 WebSocket 握手状态

可以使用带完整升级请求头的 curl 命令进行基础握手测试:
curl --http1.1 -i -N \
-H 'Connection: Upgrade' \
-H 'Upgrade: websocket' \
-H 'Sec-WebSocket-Version: 13' \
-H 'Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==' \
http://127.0.0.1:3000/ws
其中:
/ws
需要替换成应用实际使用的 WebSocket 路径。
如果上游支持该路径,可能返回:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
如果直接测试上游已经失败,问题通常位于应用程序或 WebSocket 路径。
如果直接访问上游成功,但经过 Nginx 后失败,则重点检查 Nginx 配置。
6. Nginx WebSocket 基础代理配置

例如后端 WebSocket 服务运行在:
127.0.0.1:3000
WebSocket 路径为:
/ws/
可以在 Nginx 中配置:
location /ws/ {
proxy_pass http://127.0.0.1:3000;
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;
proxy_read_timeout 300s;
proxy_send_timeout 300s;
proxy_buffering off;
}
保存以后检查 Nginx:
nginx -t
确认没有错误后重新加载:
systemctl reload nginx
不要在 nginx -t 仍然报错时强制重启 Nginx。
7. 推荐使用 map 设置 Connection 请求头
如果同一个 Nginx 站点既代理普通 HTTP,也代理 WebSocket,可以在 http 区块中加入:
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
然后在代理位置中使用:
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
完整示例:
http {
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
listen 80;
server_name example.com;
location /ws/ {
proxy_pass http://127.0.0.1:3000;
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_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 300s;
}
}
}
这样普通 HTTP 请求没有 Upgrade 头时,可以使用正常的连接处理方式。
8. proxy_http_version 还需要写吗?
较旧版本的 Nginx 代理上游时默认使用 HTTP/1.0,因此 WebSocket 配置通常需要明确写:
proxy_http_version 1.1;
较新的 Nginx 版本已经将代理上游的默认版本调整为 HTTP/1.1。
不过为了让配置更直观,也为了兼容不同服务器上的 Nginx 版本,教程中仍然建议保留:
proxy_http_version 1.1;
这样以后迁移配置时不容易遗漏。
9. WebSocket 返回 400 怎么办?
WebSocket 握手返回:
400 Bad Request
常见原因包括:
- 没有正确传递 Upgrade 请求头
- 没有正确传递 Connection 请求头
- WebSocket 路径填写错误
- 后端程序不支持该路径
- Host 或 Origin 不符合后端要求
- 请求经过多层代理,其中一层没有转发升级头
- 前端连接地址使用错误协议
首先检查:
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
然后确认前端使用的地址,例如:
ws://example.com/ws/
与 Nginx 的:
location /ws/
以及后端实际 WebSocket 路径一致。
10. WebSocket 返回 502 怎么办?
如果 Nginx 返回:
502 Bad Gateway
通常说明 Nginx 无法正常连接上游。
查看 Nginx 错误日志:
tail -n 100 /var/log/nginx/error.log
如果看到:
connect() failed (111: Connection refused) while connecting to upstream
说明上游端口没有程序接受连接。
检查:
ss -lntp | grep 3000
如果上游运行在 Docker 中:
docker ps -a
docker logs --tail 100 容器名称
如果错误日志中出现:
host not found in upstream
则可能是 Docker 服务名、DNS 或 Nginx 网络配置问题。
11. 检查 proxy_pass 使用的是 HTTP 还是 HTTPS
如果后端实际使用:
http://127.0.0.1:3000
Nginx 应配置:
proxy_pass http://127.0.0.1:3000;
如果后端实际启用了 TLS:
https://127.0.0.1:3000
才使用:
proxy_pass https://127.0.0.1:3000;
如果协议写错,可能出现:
502;
SSL handshake failed;
upstream prematurely closed connection;
其他上游连接异常。
不要因为外部域名使用 HTTPS,就认为内部上游也必须使用 HTTPS。
外部 HTTPS 和内部代理协议是两层独立配置。
12. proxy_pass 后面的斜杠会影响路径吗?
会。
例如:
location /ws/ {
proxy_pass http://127.0.0.1:3000;
}
通常会把原始的 /ws/ 路径继续传给上游。
而:
location /ws/ {
proxy_pass http://127.0.0.1:3000/;
}
可能会把匹配到的 /ws/ 前缀替换掉。
例如客户端请求:
/ws/socket
第一种配置可能让后端收到:
/ws/socket
第二种配置可能让后端收到:
/socket
如果后端只监听其中一个路径,就可能出现 400、404 或握手失败。
因此修改斜杠之前,应先确认应用实际需要的路径。
13. HTTPS 页面应该使用 wss://
如果网页通过:
https://example.com
访问,前端 WebSocket 通常也应该使用:
wss://example.com/ws/
而不是:
ws://example.com/ws/
HTTPS 页面中连接不安全的 ws://,可能被浏览器作为混合内容拦截。
常见前端写法:
const protocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
const socket = new WebSocket(`${protocol}//${location.host}/ws/`);
这样 HTTP 页面使用 ws://,HTTPS 页面使用 wss://。
14. wss:// 是否需要在 Nginx 中单独配置?
外部使用:
wss://
通常代表客户端通过 TLS 连接 Nginx。
Nginx 的 443 站点负责:
SSL 证书;
TLS 解密;
WebSocket 协议升级;
然后再代理到内部上游。
例如:
server {
listen 443 ssl;
server_name example.com;
ssl_certificate /path/fullchain.pem;
ssl_certificate_key /path/privkey.pem;
location /ws/ {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
}
}
内部上游可以继续使用普通 HTTP,只要服务器内部网络环境符合安全要求。
15. WebSocket 连接约一分钟后断开怎么办?
如果 WebSocket 建立成功,但空闲一段时间后自动断开,可能与代理读取超时有关。
可以适当增加:
proxy_read_timeout 300s;
例如:
location /ws/ {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 300s;
proxy_send_timeout 300s;
}
但更合理的方案通常还包括:
后端定期发送 WebSocket Ping;
客户端处理 Pong;
应用具备断线重连;
避免连接长时间完全无数据。
不要只把超时时间设置得无限大,而完全忽略应用自身的心跳机制。
16. proxy_buffering 是否需要关闭?
对于持续实时传输的数据,可以配置:
proxy_buffering off;
WebSocket 升级以后,Nginx 会进入特殊的隧道代理模式。
不过在同一 location 中还可能存在普通 HTTP 请求时,关闭代理缓冲有助于避免部分实时响应被延迟。
是否需要关闭,应根据应用实际情况决定。
17. 检查浏览器开发者工具
在浏览器中按:
F12
进入开发者工具。
打开:
Network
然后筛选:
WS
重新刷新页面。
正常的 WebSocket 请求应该能够看到:
101 Switching Protocols
点击具体连接,可以查看:
Headers;
Messages;
Frames;
关闭状态。
如果显示:
400;
403;
404;
502;
就可以根据状态码继续排查。
浏览器 Console 中的错误信息也很重要。
18. 101、200、400、404、502 分别说明什么?
101
101 Switching Protocols
表示 WebSocket 协议升级成功。
200
返回普通 HTTP 200,但没有升级,通常说明:
请求没有进入正确的 WebSocket 路径;
升级请求头没有正确处理;
后端把请求当作普通 HTTP。
400
通常和握手请求、请求头、Origin 或路径有关。
404
通常说明 Nginx location 或后端 WebSocket 路径错误。
502
通常说明 Nginx 无法连接上游,或者上游异常关闭连接。
19. 检查 Nginx 实际加载的配置
有时修改了某个网站配置文件,但 Nginx 实际没有加载。
执行:
nginx -T
可以输出完整的 Nginx 配置。
搜索 WebSocket 路径:
nginx -T 2>&1 | grep -n "Upgrade"
或者:
nginx -T 2>&1 | grep -n "/ws/"
确认实际加载的配置中存在:
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
修改后记得执行:
nginx -t
和:
systemctl reload nginx
20. 查看 Nginx 访问日志和错误日志
查看错误日志:
tail -f /var/log/nginx/error.log
查看访问日志:
tail -f /var/log/nginx/access.log
然后在浏览器中重新连接一次 WebSocket。
观察:
请求路径;
状态码;
上游地址;
具体错误。
如果不同站点使用独立日志,应查看该网站配置中指定的日志文件。
21. Nginx 在宿主机,应用在 Docker 中
如果 Nginx 安装在宿主机,而应用运行在 Docker 中,最简单的方式通常是给应用映射一个仅本机可访问的端口。
例如 Compose:
services:
app:
image: example/app:latest
ports:
- "127.0.0.1:3000:3000"
Nginx 配置:
proxy_pass http://127.0.0.1:3000;
这样端口不会直接暴露到整个公网,只供宿主机 Nginx 访问。
检查:
docker ps
应该看到:
127.0.0.1:3000->3000/tcp
22. Nginx 和应用都在 Docker 中

如果 Nginx 和应用都在同一个 Docker Compose 项目中,可以通过服务名访问。
例如:
services:
nginx:
image: nginx:alpine
app:
image: example/app:latest
Nginx 中使用:
proxy_pass http://app:3000;
这里:
app
是 Compose 服务名。
不要在 Nginx 容器中使用:
127.0.0.1:3000
访问另一个容器。
因为容器中的:
127.0.0.1
只代表当前 Nginx 容器自己。
23. 不要使用固定容器 IP
Docker 容器重新创建以后,IP 地址可能发生变化。
例如原来应用容器是:
172.19.0.3
重新创建后可能变成:
172.19.0.5
如果 Nginx 写死:
proxy_pass http://172.19.0.3:3000;
容器更新后就可能出现 502。
同一 Compose 网络中应该优先使用:
proxy_pass http://app:3000;
通过稳定的服务名访问。
24. 检查 Docker 服务名是否能解析
如果 Nginx 在容器中,可以执行:
docker exec nginx getent hosts app
如果能够返回应用容器 IP,说明服务名解析正常。
再测试上游:
docker exec nginx curl -I http://app:3000/health
如果服务名无法解析,需要检查:
Nginx 和 app 是否在同一 Docker Network;
服务名是否正确;
Compose 网络是否正常。
查看网络:
docker network ls
查看项目网络:
docker network inspect 项目名称_default
25. Host 请求头会影响 WebSocket 吗?
有些后端应用会校验:
Host;
Origin;
域名白名单。
通常可以传递原始 Host:
proxy_set_header Host $host;
如果后端要求包含端口,可以根据情况使用:
proxy_set_header Host $http_host;
如果应用日志提示:
Invalid Host;
Origin not allowed;
Forbidden;
就需要检查应用自身的域名和 Origin 白名单配置。
不要为了临时解决问题完全关闭所有来源校验。
26. Origin 校验失败怎么办?
WebSocket 握手可以携带:
Origin
部分应用会检查 Origin 是否属于允许的域名。
例如外部访问:
https://chat.example.com
但后端只允许:
http://localhost
就可能拒绝连接。
应该在应用配置中增加真实域名,而不是在 Nginx 中随意清空 Origin。
只有明确知道应用要求时,才考虑修改:
proxy_set_header Origin
相关配置。
27. WebSocket 路径写错怎么办?
假设应用真实 WebSocket 路径为:
/socket.io/
但 Nginx 配置:
location /ws/ {
前端又连接:
/ws/
这种情况下普通网页可以正常打开,但 WebSocket 一直失败。
排查时必须同时确认:
前端连接路径;
Nginx location;
proxy_pass 路径;
后端应用监听路径。
四者需要匹配。
28. Socket.IO 和原生 WebSocket 一样吗?
Socket.IO 会在 WebSocket 之外增加自己的握手和降级机制。
常见路径为:
/socket.io/
如果代理 Socket.IO,通常需要确认:
路径;
版本;
传输方式;
查询参数;
上游服务。
不要把所有 WebSocket 应用都机械配置成:
/ws/
应该以应用实际文档和网络请求为准。
29. 多层反向代理需要每一层都支持升级
如果请求经过:
CDN;
负载均衡;
第一层 Nginx;
第二层 Nginx;
后端应用,
那么每一层都需要正确支持 WebSocket 协议升级。
只在最后一层配置:
Upgrade
并不能保证前面的代理层也会正确转发。
遇到多层代理时,可以逐层绕过测试:
客户端直接访问后端;
访问内层代理;
访问外层代理;
从而确认故障具体发生在哪一层。
30. WebSocket 快速排查顺序
建议按照下面的顺序处理。
第一步:确认上游程序
ss -lntp | grep 上游端口
第二步:直接测试上游
curl -I http://127.0.0.1:上游端口
第三步:检查 WebSocket 路径
确认前端、Nginx 和后端路径一致。
第四步:检查升级请求头
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
第五步:检查 Nginx 配置
nginx -t
nginx -T
第六步:查看日志
tail -f /var/log/nginx/error.log
第七步:如果使用 HTTPS
确认前端使用:
wss://
第八步:如果使用 Docker
检查:
服务名;
Docker Network;
容器状态;
容器端口。
31. 常见问题对照表
| 问题现象 | 优先检查 |
|---|---|
| 返回 101 | WebSocket 握手成功 |
| 返回 200 | 请求未完成协议升级 |
| 返回 400 | 请求头、路径、Origin |
| 返回 404 | location 或后端路径 |
| 返回 502 | 上游服务、端口、Docker |
| 约一分钟后断开 | proxy_read_timeout、心跳 |
| HTTPS 页面连接失败 | 是否使用 wss:// |
| 容器更新后突然 502 | 是否写死容器 IP |
| Nginx 容器无法访问 app | Compose 网络、服务名 |
| 普通网页正常但实时功能失效 | WebSocket 代理配置 |
32. 服务器部署建议
WebSocket 本身通常不会消耗特别高的服务器资源,但长连接数量增加以后,需要考虑:
并发连接;
文件描述符;
后端程序内存;
网络带宽;
心跳频率;
应用处理能力。
如果需要部署聊天系统、在线终端、实时通知、Docker 应用或其他 WebSocket 项目,可以根据并发量选择莱卡云 Linux 云服务器。
查看官网购买链接: https://www.lcayun.com
如果需要学习 Nginx 基础反向代理,可以参考:
相关阅读: Nginx 反向代理配置教程
如果 Docker 项目越来越多,可以参考:
相关阅读: 使用 Docker 搭建 Dockge:可视化管理 Docker Compose 项目
如果公网端口本身无法访问,可以参考:
相关阅读: 云服务器端口不通怎么办?
33. 总结
Nginx 反向代理 WebSocket 失败时,最重要的是确认:
上游服务是否正常,以及协议升级请求头是否被正确转发。
正常握手应该返回:
101 Switching Protocols
基础代理配置应包含:
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
如果返回:
400
重点检查请求头、路径和 Origin。
如果返回:
502
重点检查上游端口、后端程序和 Docker 网络。
如果 HTTPS 页面无法连接,需要使用:
wss://
如果连接建立后空闲一段时间断开,可以检查:
proxy_read_timeout
以及应用的 WebSocket 心跳机制。
如果 Nginx 和应用都运行在 Docker 中,应优先使用 Compose 服务名,而不是固定容器 IP。
正确排查顺序是:
上游服务 → 握手状态 → Upgrade 请求头 → 代理路径 → HTTPS/WSS → Docker 网络 → 超时与心跳。








