Vite配置多主机名证书报错:socket.setSecureContext is not a function
解决Vite开发服务器多主机名动态证书配置的
socket.setSecureContext错误 问题背景
开发阶段需要根据访问的主机名自动切换对应SSL证书(用于保证Cookie安全等场景),无需修改服务器核心配置,编写了Vite插件代码后,证书能正常加载,但运行时抛出错误:
Uncaught TypeError: socket.setSecureContext is not a function
原配置代码如下:
import { defineConfig } from 'vite' import fs from 'fs' import tls from 'tls' import mkcert from 'vite-plugin-mkcert' import react from '@vitejs/plugin-react-swc' import path from 'path' import tailwindcss from 'tailwindcss' const myPlugin = () => ({ name: 'configure-server-certs', configureServer(server) { server.host = '0.0.0.0' const certificates: Record<string, { key: Buffer; cert: Buffer }> = { 'admin-127-0-0-1.sslip.io': { cert: fs.readFileSync('./certs/admin-127-0-0-1.sslip.io.pem'), key: fs.readFileSync('./certs/admin-127-0-0-1.sslip.io-key.pem'), }, 'dark-127-0-0-1.sslip.io': { cert: fs.readFileSync('./certs/dark-127-0-0-1.sslip.io.pem'), key: fs.readFileSync('./certs/dark-127-0-0-1.sslip.io-key.pem'), }, 'light-127-0-0-1.sslip.io': { cert: fs.readFileSync('./certs/light-127-0-0-1.sslip.io.pem'), key: fs.readFileSync('./certs/light-127-0-0-1.sslip.io-key.pem'), }, } server.httpServer?.on('secureConnection', socket => { const hostname = socket.servername console.log(hostname) const cert = certificates[hostname] console.log(cert) if (cert) { const ctx = tls.createSecureContext(cert) socket.setSecureContext(ctx) } else { console.log(`No certificate found for ${hostname}`) } }) }, }) export default defineConfig({ server: { host: '0.0.0.0', port: 5173, }, plugins: [mkcert(), myPlugin(), react()], resolve: { alias: { '@': path.resolve(__dirname, './src'), }, }, css: { postcss: { plugins: [tailwindcss()], }, }, })
问题原因
secureConnection事件触发时,TLS握手已经完成,此时的TLSSocket实例不再允许调用setSecureContext方法——该方法仅能在握手开始前使用。直接修改已建立连接的socket上下文不符合Node.js TLS API的设计逻辑。
正确解决方案
通过配置SNICallback(Server Name Indication回调),在TLS握手阶段动态返回对应主机名的证书上下文。修改后的插件代码如下:
import { defineConfig } from 'vite' import fs from 'fs' import tls from 'tls' import mkcert from 'vite-plugin-mkcert' import react from '@vitejs/plugin-react-swc' import path from 'path' import tailwindcss from 'tailwindcss' const myPlugin = () => ({ name: 'configure-server-certs', configureServer(server) { server.host = '0.0.0.0' // 预创建所有证书的SecureContext实例,提升性能 const certContexts: Record<string, tls.SecureContext> = { 'admin-127-0-0-1.sslip.io': tls.createSecureContext({ cert: fs.readFileSync('./certs/admin-127-0-0-1.sslip.io.pem'), key: fs.readFileSync('./certs/admin-127-0-0-1.sslip.io-key.pem'), }), 'dark-127-0-0-1.sslip.io': tls.createSecureContext({ cert: fs.readFileSync('./certs/dark-127-0-0-1.sslip.io.pem'), key: fs.readFileSync('./certs/dark-127-0-0-1.sslip.io-key.pem'), }), 'light-127-0-0-1.sslip.io': tls.createSecureContext({ cert: fs.readFileSync('./certs/light-127-0-0-1.sslip.io.pem'), key: fs.readFileSync('./certs/light-127-0-0-1.sslip.io-key.pem'), }), } // 获取服务器的TLS配置,设置SNI回调 const tlsOptions = server.httpServer?.options as tls.SecureContextOptions & tls.TLSServerOptions if (tlsOptions) { // 保存默认上下文(来自mkcert插件) const defaultContext = tlsOptions.SNICallback?.('', null) || tls.createSecureContext(tlsOptions) // 自定义SNI回调 tlsOptions.SNICallback = (servername, cb) => { console.log(`Received SNI request for: ${servername}`) const context = certContexts[servername] if (context) { cb(null, context) } else { console.log(`No certificate found for ${servername}, using default`) cb(null, defaultContext) } } } }, }) export default defineConfig({ server: { host: '0.0.0.0', port: 5173, }, plugins: [mkcert(), myPlugin(), react()], resolve: { alias: { '@': path.resolve(__dirname, './src'), }, }, css: { postcss: { plugins: [tailwindcss()], }, }, })
关键修改说明
- 预创建SecureContext:提前为每个证书生成
SecureContext实例,避免每次请求都重复读取文件和创建上下文,提升性能。 - 使用SNICallback:在TLS握手阶段,客户端会发送SNI(访问的主机名),通过该回调匹配对应的证书上下文,符合Node.js TLS API的使用规范。
- 保留默认上下文:当没有匹配到对应主机名的证书时,回退到
mkcert插件生成的默认证书,避免连接失败。
内容的提问来源于stack exchange,提问作者user672009
相关产品推荐
相关产品推荐

