欢迎光临
我们一直在努力

Nginx WebSocket 400/502 排查教程

在使用 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检查WebSocket握手和101状态

可以使用带完整升级请求头的 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 基础代理配置

Nginx配置WebSocket反向代理Upgrade请求头

例如后端 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 中

Docker Compose检查Nginx上游服务名和网络

如果 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. 常见问题对照表

问题现象优先检查
返回 101WebSocket 握手成功
返回 200请求未完成协议升级
返回 400请求头、路径、Origin
返回 404location 或后端路径
返回 502上游服务、端口、Docker
约一分钟后断开proxy_read_timeout、心跳
HTTPS 页面连接失败是否使用 wss://
容器更新后突然 502是否写死容器 IP
Nginx 容器无法访问 appCompose 网络、服务名
普通网页正常但实时功能失效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 网络 → 超时与心跳。

赞(0)
未经允许不得转载:莱卡云 » Nginx WebSocket 400/502 排查教程