websocket握手失败应先查浏览器控制台错误:如err_connection_refused(后端未启动)、err_cert_authority_invalid(自签名证书不被信任)、err_connection_closed(wss升级失败)等,对应tls或跨域问题,而非js逻辑错误。

WebSocket握手失败时,先看浏览器控制台报什么错误
绝大多数 WebSocket 连接失败不是代码写错了,而是卡在 TLS 层或跨域策略上。打开浏览器开发者工具的 Console 和 Network 标签页,重点找这几类错误:net::ERR_CONNECTION_REFUSED(后端没起来)、net::ERR_CERT_AUTHORITY_INVALID(自签名证书不被信任)、ERR_CONNECTION_CLOSED(wss 升级失败)、Failed to construct 'WebSocket': The URL's scheme must be either 'ws:' or 'wss:'(协议写成 http://)。这些错误直接对应排查路径,别急着改 JS 逻辑。
wss 连接必须用有效证书,自签名证书在 Chrome/Firefox 中默认不认
本地开发时用 self-signed cert + wss://localhost:8080,浏览器会直接拒绝建立连接——这不是 WebSocket 实现的问题,是 TLS 握手阶段就被拦截了。解决方法只有两个:
- 用
mkcert生成被系统信任的本地证书(推荐),然后让服务端加载cert.pem和key.pem - 临时在 Chrome 地址栏输入
chrome://flags/#unsafely-treat-insecure-origin-as-secure,把你的https://localhost:8080加进去并启用,但仅限调试,不可用于测试环境 - 不要尝试用
WebSocket连http://地址再靠反向代理升成 wss —— 浏览器校验的是你 JS 里写的 URL 协议,不是后端实际走的协议
Node.js 后端用 ws 库时,wss 服务必须显式传入 https.Server 实例
ws 库本身不处理 TLS,它只负责 WebSocket 协议层。如果你直接 new WebSocket.Server({ port: 443 }),它起的是纯 ws 服务,根本不会响应 wss 请求。正确做法是:
- 用
https.createServer({ key, cert }, app)创建一个 HTTPS 服务实例 - 把这个实例传给
new WebSocket.Server({ server }),而不是传port - 确保
key和cert是 PEM 格式且路径可读,常见坑是用了 PKCS#12(.pfx)但没转换 - 如果用 Nginx 做反向代理,后端仍要跑 ws(非 wss),但 Nginx 需配置
proxy_http_version 1.1和Upgrade $http_upgrade,否则 upgrade 请求被丢弃
前端 new WebSocket() 时,URL 必须和页面当前协议严格一致
从 https://myapp.com 页面发起 new WebSocket('ws://api.com') 一定会失败,现代浏览器禁止混合内容。即使后端支持 ws,也必须写成 wss://api.com,且该域名证书必须覆盖 api.com(不能只覆盖 www.api.com)。
立即学习“前端免费学习笔记(深入)”;
- 开发时若用
localhost,注意证书是否包含localhostSAN 条目;很多工具生成的证书默认不含它 - 避免硬编码 URL,用
window.location.origin.replace(/^http/, 'ws')这类动态推导容易出错,不如统一配环境变量 - 某些 CDN 或中间件会缓存 101 响应,导致握手失败后重试仍返回 200,此时要检查响应头是否有
Connection: upgrade和Upgrade: websocket
真正卡住人的从来不是 WebSocket API 本身,而是证书链完整性、反向代理的 upgrade 头透传、以及浏览器对混合协议的零容忍——这些地方一漏,控制台就只报“connection closed before receiving a handshake response”,但实际连 TLS 握手都没过。











