wss需通过直连模式(服务端加载证书私钥)或反向代理模式(如nginx配置ssl_certificate及upgrade头)实现,证书须由可信ca签发且域名严格匹配。

WSS 不是“给 WebSocket 单独配个证书”,而是让 WebSocket 运行在 TLS 加密通道上——要么服务端自己加载证书(直连模式),要么由 Nginx/Apache 终止 SSL 后透传升级连接(反向代理模式)。选哪种,取决于你用的是什么框架、部署结构是否允许暴露 443 端口、以及是否愿意让业务服务器承担加解密开销。
Node.js + ws 库:用 https.createServer 加载证书
ws 库本身不直接支持 ssl 选项(旧文档里写的 ssl: { cert, key } 是误导或已废弃),必须套一层 https.createServer。否则会报错 Error: listen EACCES: permission denied 0.0.0.0:443 或连接被重置。
-
cert必须是完整证书链(fullchain.pem),不能只用cert.pem;否则 Safari 和部分 Android 客户端握手失败 -
key文件权限必须为600,否则fs.readFileSync报EPERM - 监听端口必须是
443(或用户有权限的端口),且不能和 HTTP 服务共用同一http.Server实例 - 客户端用
new WebSocket('wss://your-domain.com'),域名必须和证书一致,否则浏览器拒绝连接
const https = require('https');
const fs = require('fs');
const { WebSocketServer } = require('ws');
const server = https.createServer({
cert: fs.readFileSync('/etc/letsencrypt/live/your-domain.com/fullchain.pem'),
key: fs.readFileSync('/etc/letsencrypt/live/your-domain.com/privkey.pem')
});
const wss = new WebSocketServer({ server });
server.listen(443);
Python websocket-server:传入 cert 和 key 参数即可
这个库对 SSL 支持很直接,但容易踩两个坑:一是路径写错导致启动时静默失败(没报错但连不上),二是证书文件格式不对(比如用了 PKCS#12 的 .pfx)。
- 确认你用的是
websocket-server(不是websockets),后者配置方式完全不同 -
cert参数必须是 PEM 格式证书(.crt或.pem),不能是.pfx或.der -
key必须是未加密的私钥(即没有密码保护),否则启动时报ValueError: Key is encrypted - 若用 Let's Encrypt,直接填
/etc/letsencrypt/live/your-domain.com/fullchain.pem和privkey.pem
from websocket_server import WebsocketServer
server = WebsocketServer(
host='0.0.0.0',
port=9001,
cert='/path/to/fullchain.pem',
key='/path/to/privkey.pem'
)
server.run_forever()
Nginx 反向代理 WSS:关键在 Upgrade 头和证书路径
这是生产环境最常用也最容易出问题的方式。Nginx 配置看起来简单,但漏掉任意一个 header 或协议版本,客户端就会卡在 HTTP 101 Switching Protocols 之前,表现为连接立刻关闭或超时。
-
ssl_certificate和ssl_certificate_key路径必须绝对正确,且 Nginx 进程有读取权限(常因 SELinux 或文件属主导致 403) -
proxy_http_version 1.1缺失会导致降级到 HTTP/1.0,Upgrade 失效 -
proxy_set_header Upgrade $http_upgrade和Connection "upgrade"必须成对出现,少一个就无法完成 WebSocket 升级 - 后端服务(如 Node.js)监听的仍是
http://localhost:3000,完全不用碰证书
server {
listen 443 ssl;
server_name your-domain.com;
ssl_certificate /etc/letsencrypt/live/your-domain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/your-domain.com/privkey.pem;
location / {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
}
}
Swoole / Laravel WebSockets / Ratchet:SSL 参数名各不相同
这些框架底层都依赖 PHP 的 OpenSSL 扩展,但参数命名五花八门,抄错一个字母就启动失败,且错误提示极其模糊(比如只报 Segmentation fault)。
- Swoole 用
ssl_cert_file和ssl_key_file,不是cert或certificate - Laravel WebSockets 要求在
config/websockets.php中配置'ssl' => ['local_cert' => ..., 'local_pk' => ...],且必须用绝对路径 - Ratchet 的
$context['ssl']['local_cert']值必须是 PEM 格式证书,不能是 DER;私钥必须是 RSA,ECDSA 私钥需额外指定'crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_2_SERVER - 所有 PHP 框架都要求 OpenSSL 版本 ≥ 1.1.1,低于此版本无法加载 Let's Encrypt 的 ECC 证书
真正麻烦的从来不是“怎么配”,而是证书链完整性、私钥权限、header 透传顺序、以及客户端域名与证书 CN/SAN 是否严格一致——这些细节一错,现象都是“连不上”,但原因天差地别。调试时优先检查 Nginx error log 或服务端启动日志,而不是反复改配置。











