
本文详解 fastify 配合 @fastify/websocket 在启用 https(即 wss)时连接失败的典型原因与完整解决路径,涵盖证书配置、服务端注册顺序、客户端连接方式及调试要点。
本文详解 fastify 配合 @fastify/websocket 在启用 https(即 wss)时连接失败的典型原因与完整解决路径,涵盖证书配置、服务端注册顺序、客户端连接方式及调试要点。
在使用 Fastify 构建全栈实时应用时,本地 HTTP 环境下 @fastify/websocket 通常能无缝工作;但一旦切换至 HTTPS(即需支持 wss:// 协议),前端 WebSocket 客户端常报错 ERR_CONNECTION_REFUSED 或 net::ERR_CERT_INVALID,而 Fastify 的 HTTPS 服务本身却正常响应 HTML/JSON —— 这表明问题不在于服务未启动,而在于 WSS 升级握手或 TLS 层配置存在隐性缺陷。
✅ 核心原因:WebSocket 依赖 HTTPS 实例的正确透传
@fastify/websocket 并非独立运行 WebSocket 服务器,而是复用 Fastify 底层 HTTP(S) Server 的 upgrade 事件。当启用 HTTPS 时,必须确保:
- Fastify 实例显式以 HTTPS 模式初始化(而非先创建 HTTP 实例再“打补丁”);
- @fastify/websocket 插件在 HTTPS 配置完成后再注册(注册顺序影响内部 server 引用);
- 证书文件路径有效、权限可读,且私钥与证书匹配(常见错误:.key 与 .crt 不属同一对)。
你的原始代码中已基本满足前两点,但存在一个关键疏漏:未为 HTTPS 模式下的 fastify.listen() 显式指定 https: true 选项(尽管 Fastify 内部会识别,但部分 Node.js 版本或代理环境要求显式声明)。此外,客户端若仍尝试连接 ws://(而非 wss://),必然失败。
✅ 正确配置示例(含开发与生产适配)
const fs = require('fs');
const fastify = require('fastify');
const config = {
privKey: process.env.PRIV_KEY || './certs/localhost-key.pem',
certKey: process.env.CERT_KEY || './certs/localhost.pem',
https: process.env.HTTPS === '1',
domains: process.env.DOMAINS?.split(',') || ['http://localhost:3000'],
port: parseInt(process.env.PORT) || 3000,
};
// ✅ 关键:统一构造 Fastify 实例,HTTPS 配置内聚
const fastifyInstance = fastify({
serverTimeout: 60 * 60 * 1000,
logger: true,
...(config.https && {
https: {
key: fs.readFileSync(config.privKey),
cert: fs.readFileSync(config.certKey),
// 可选:添加 ca 字段(如使用自签名中间 CA)
// ca: fs.readFileSync('./certs/rootCA.pem'),
}
})
});
// ✅ 关键:CORS 需明确允许 wss:// 协议源(注意协议一致性)
fastifyInstance.register(require('@fastify/cors'), {
origin: config.domains.map(domain =>
domain.replace(/^http/, 'ws') // 将 http:// → ws://, https:// → wss://
),
credentials: true
});
// ✅ 关键:WebSocket 插件必须在 HTTPS 配置后注册
fastifyInstance.register(require('@fastify/websocket'));
// WebSocket 路由定义
fastifyInstance.register(async function (instance) {
instance.get('/live', { websocket: true }, (connection, request) => {
console.log(`New WSS connection from ${request.socket.remoteAddress}`);
connection.socket.on('message', (data) => {
try {
const msg = data.toString();
console.log('Received:', msg);
connection.socket.send(`Echo: ${msg}`);
} catch (err) {
console.error('Send error:', err);
}
});
connection.socket.on('close', () => {
console.log('Connection closed');
});
});
});
// ✅ 关键:listen 时无需额外传 https: true,但 host/port 必须显式
const start = async () => {
try {
const address = await fastifyInstance.listen({
host: '0.0.0.0',
port: config.port,
// ⚠️ 注意:HTTPS 模式下,Node.js 原生 server 会自动处理 upgrade,
// 无需手动设置 secureContext —— Fastify 已封装
});
fastifyInstance.log.info(`Server listening at ${address}`);
if (config.https) {
fastifyInstance.log.info(`WSS endpoint: wss://localhost:${config.port}/live`);
} else {
fastifyInstance.log.info(`WS endpoint: ws://localhost:${config.port}/live`);
}
} catch (err) {
fastifyInstance.log.error(err);
process.exit(1);
}
};
start();✅ 开发环境证书推荐方案:mkcert
正如答案所提示,自签名证书的兼容性是最大障碍。浏览器和现代 WebSocket 客户端(如 Chrome、Firefox、new WebSocket())默认拒绝不受信任的证书。解决方案:
-
安装 mkcert(官网):
# macOS brew install mkcert nss mkcert -install # Windows (PowerShell as Admin) choco install mkcert mkcert -install
-
生成本地可信证书:
mkdir certs mkcert -key-file certs/localhost-key.pem -cert-file certs/localhost.pem "localhost"
-
设置环境变量启动:
HTTPS=1 PRIV_KEY=./certs/localhost-key.pem CERT_KEY=./certs/localhost.pem PORT=3000 node server.js
此时 wss://localhost:3000/live 将被浏览器完全信任。
✅ 客户端连接注意事项
确保前端使用正确的协议与域名:
// ✅ 正确:协议与后端一致
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
const socket = new WebSocket(`${protocol}//${window.location.host}/live`);
socket.onopen = () => console.log('WSS connected');
socket.onmessage = (e) => console.log('Server:', e.data);若部署在 Nginx/Apache 后,还需配置反向代理透传 WebSocket 头(Upgrade, Connection),此非 Fastify 侧问题,但常被忽略。
✅ 总结:排查清单
| 检查项 | 是否满足 | 说明 |
|---|---|---|
| ✅ Fastify 实例初始化时已传入 https: { key, cert } | ☐ | 避免 HTTP 实例误用 |
| ✅ @fastify/websocket 在 HTTPS 配置后注册 | ☐ | 确保插件绑定到正确 server |
| ✅ 证书文件路径正确、可读、配对无误 | ☐ | openssl x509 -in cert.pem -text -noout 验证 |
| ✅ 客户端使用 wss://(非 ws://)连接 HTTPS 服务 | ☐ | 协议必须严格匹配 |
| ✅ 开发环境使用 mkcert 生成的可信证书 | ☐ | 规避浏览器证书警告 |
| ✅ 防火墙放行端口,且无中间代理拦截 Upgrade 请求 | ☐ | curl -i -N -H "Connection: Upgrade" -H "Upgrade: websocket" https://yoursite/live 测试 |
遵循以上结构化配置,即可稳定启用 Fastify 的 WSS 支持,兼顾开发效率与生产安全性。










