WebSocket 连不上?五个原因的排查顺序
WebSocket 的失败很少是代码写错 —— 多数卡在握手阶段,而握手由浏览器与中间设备共同把关。五个原因按出现频率排列,前两个占了大多数。
- HTTPS 页面里连 ws:// 会被浏览器按混合内容直接拦掉,必须换成 wss://,本地调试才用 http 页面
- 多数框架默认只接受同源或白名单里的 Origin,而浏览器握手带的是本站域名,很容易被判 403
- Nginx 需要显式转发 Upgrade 与 Connection 头,否则握手根本到不了后端应用
- 握手既不 open 也不 error 时,通常是链路被中间设备静默丢弃,这时候该查网络而不是改代码
- 浏览器能设的请求头非常有限,需要 token 或 Cookie 鉴权的接口用网页工具连不上,得换成客户端代码
#先判断失败发生在哪个阶段
WebSocket 的失败分两个阶段:握手没成功,或者握手成功但连接立刻断开。 两者的原因完全不重叠,混淆了就会在错误的方向上改代码。
判据很直接:日志里有没有出现「已连接」。没有就是握手失败,有就是连上之后断的。
| 阶段 | 表现 | 排查方向 |
|---|---|---|
| 握手失败 | 没有「已连接」记录,只有 error | 协议、Origin、路径、代理、证书 |
| 连上就断 | 有「已连接」随即 close | 服务端逻辑、心跳、鉴权超时 |
| 一直连接中 | 既不 open 也不 error | 链路被中间设备丢弃 |
用 WebSocket 测试 时,握手超过 10 秒没完成,日志会主动提示一次 —— 这个提示就是为「一直连接中」这种情况准备的。
#混合内容是第一位原因
HTTPS 页面里连 ws:// 会被浏览器直接拦掉,这不是服务端拒绝,是浏览器根本没发出去。 它属于混合内容策略,报错通常出现在控制台而不是网络面板。
规则很简单:
- 页面是
https://,连接必须用wss:// - 页面是
http://,ws://与wss://都可以
所以「本地能连、线上连不上」的经典组合,往往就是本地用 http 页面配了 ws://。线上换成 wss 之后,如果服务端没有配置 TLS,就是另一个问题了(证书部分会说)。
混合内容是浏览器层面的拦截,服务端日志里什么都不会留下。如果服务端「看不到任何连接尝试」,先检查页面协议,别去查服务端。
#Origin 校验:服务端主动拒绝
多数 WebSocket 框架默认只接受同源或白名单里的 Origin,而浏览器发起的握手带的是发起页面的域名。 用一个在线工具去连生产环境的接口,Origin 就是工具的域名,被拒是正常的。
这类失败的典型特征是:握手返回 403,且服务端日志里能看到明确拒绝记录 —— 与服务端「根本没收到」正好相反。
处理方式取决于你站在哪一边:
- 如果你是服务端:把测试用的 Origin 加进白名单,或者对公开接口关掉 Origin 校验
- 如果你是调用方:换成能设 Origin 的客户端(代码里的 WebSocket 客户端通常可设,浏览器里不行)
Origin 校验不是安全认证。它可以被非浏览器客户端随意伪造,防的是「其他网站借用用户浏览器连你的服务」,不是防「有心人直接连」。
#路径与代理:握手到不了后端
两个基础设施问题占这一类的大半:路径写错,以及反向代理没有转发升级头。 它们都表现为「连不上」,但修法完全不同。
路径问题:多数库默认挂在 /ws 或 /websocket,只填 wss://host 往往返回 404。带上完整路径再试一次。
代理问题更隐蔽:Nginx 需要显式转发 Upgrade 与 Connection 头,否则请求会被当成普通 HTTP 请求处理,握手到不了后端。
# 关键在两行 Upgrade / Connection,缺了握手永远不成功
location /ws {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}注意 proxy_http_version 1.1 也不能少 —— HTTP/1.0 不支持协议升级。Cloudflare 之类的 CDN 需要单独开启 WebSocket 支持。
#证书不被信任:自签的直接失败
wss:// 走的是标准 TLS 握手,自签证书会直接失败,浏览器不给「继续访问」的机会。 这和访问自签 HTTPS 网站时的体验不同,那里至少还能点「高级 → 继续」。
排查办法:用浏览器直接打开 https:// 的那个域名,看证书是否受信任。如果浏览器也报警,那 wss 必然失败。
内网自签证书的场景,可行的做法是让客户端信任该证书(导入根证书),或者用 http 页面配 ws:// 做本地调试。
#顺手确认域名与端口
握手失败也可能是最基础的问题:域名解析错了,或者目标端口根本没在监听。 这两项用工具几分钟就能排除,比读代码快。
按顺序排一遍:
- 用 DNS 查询 确认域名解析到预期的 IP —— 尤其刚改过解析时,TTL 缓存会让不同网络环境拿到不同结果
- 用 端口大全 确认目标端口是否常用服务端口,以及防火墙该放行什么。
wss走 443、ws走 80,但自定义端口也很常见(如 8080、3000) - 用 IP 地址查询 判断解析出来的是公网还是私网地址。如果连的是
10.x、172.16-31.x、192.168.x这类私有地址,从公网环境连必然不通
浏览器直连目标服务器,消息不经过任何中转 —— 好处是没有中间人,代价是目标服务器能看到你的真实 IP。连陌生地址前,先确认这个地址可信。
#什么时候该用哪个工具
| 你想确认的事 | 用哪个 | 关键点 |
|---|---|---|
| 握手能不能成功 | WebSocket 测试 | 只处理文本帧,不支持自定义头 |
| 域名解析到哪个 IP | DNS 查询 | TTL 决定缓存还要多久 |
| 地址是公网还是私网 | IP 地址查询 | 私有地址从公网连不通 |
| 端口该不该放行 | 端口大全 | 三段端口范围权限不同 |
| 服务端到底回了什么码 | HTTP 状态码 | 403 与 404 指向不同原因 |
顺序建议:先确认协议(wss?)→ 再确认路径与代理 → 然后 Origin → 最后证书与网络。 前两步不需要碰服务端代码,排查成本最低。
#常见问题
#本地能连,部署到线上就连不上?
最常见的是混合内容:线上页面是 https,代码里还在连 ws://。其次是代理没转发 Upgrade 头 —— 本地直连后端没有代理这一层,所以本地正常。这两条按顺序查,通常第一次就命中。
#握手返回 403,是权限问题吗?
在 WebSocket 场景里,403 更常见的原因是 Origin 被拒,而不是鉴权失败。看服务端日志里有没有提到 Origin 或握手拒绝;如果日志里完全没有记录,那说明请求根本没到应用层,问题在代理。
#连接一直停在「连接中」不动?
说明握手既没成功也没失败 —— 请求被中间设备静默丢弃了。常见于企业防火墙、某些代理或不支持 WebSocket 的 CDN。先确认链路上有没有这类设备,再考虑换网络环境测试。
#需要带 token 才能连,能用网页工具测吗?
不能。浏览器提供的 WebSocket API 无法设置自定义请求头,所以 Authorization 加不上。可行的做法是用 Cookie 鉴权(同源时浏览器会自动带上),或者用 Postman、代码里的客户端测试。
#为什么收到的消息是乱码?
这个工具只处理文本帧,二进制帧不会按协议解析。如果服务端发的是二进制数据(图片分片、protobuf、压缩后的 JSON),需要按协议自己解。另外中文乱码要先确认服务端是不是按 UTF-8 发送的。