一、为什么本地开发要启用 HTTPS / HTTP2?
在现代 Web 开发中,越来越多的特性(如 Service Worker、WebRTC、Push API、HTTP/2 等)仅在安全上下文(HTTPS)下可用。
如果你的本地环境仍然运行在 http://localhost,可能会遇到以下问题:
- 浏览器报错:“此 API 仅在安全上下文中可用”
- WebSocket、WebRTC、Push 等无法工作
- 与生产环境行为不一致(如 cookie
SameSite、secure属性)
而 HTTP2 不仅能开启多路复用、头部压缩,还可显著加快静态资源加载速度。
👉 因此,在本地启用 HTTPS + HTTP2 是开发提效和调试的关键一步。
二、什么是 mkcert?
mkcert 是一个零配置、跨平台的本地 CA(Certificate Authority)工具。
它可以创建并信任自签名证书,让你的本地 HTTPS 环境完全受浏览器信任,不再出现「不安全」警告。
✨ 核心特点
- 自动生成根证书并导入系统信任库
- 为任意域名(含 localhost、127.0.0.1、自定义域)生成证书
- 一键启用 HTTPS,完全离线可用
- 支持 macOS / Windows / Linux / WSL
三、安装 mkcert
macOS
brew install mkcert
brew install nss # 如果使用 Firefox
Windows
使用 Chocolatey:
choco install mkcert
或下载 可执行文件。
Linux
sudo apt install libnss3-tools
brew install mkcert # 或手动下载
四、生成本地 HTTPS 证书
1️⃣ 初始化本地 CA
mkcert -install
这一步会在系统中安装一个根证书,使浏览器信任由它签发的证书。
2️⃣ 生成证书
mkcert localhost 127.0.0.1 ::1
生成文件:
localhost+2.pem # 证书文件
localhost+2-key.pem # 私钥文件
你也可以指定自定义域名:
mkcert myproject.local
📝 提示:你可以在 hosts 文件中添加
127.0.0.1 myproject.local。
五、在不同服务中使用 HTTPS / HTTP2
1️⃣ Nginx 配置
server {
listen 443 ssl http2;
server_name localhost;
ssl_certificate /path/to/localhost+2.pem;
ssl_certificate_key /path/to/localhost+2-key.pem;
root /Users/you/project/dist;
index index.html;
location / {
try_files $uri $uri/ =404;
}
}
server {
listen 80;
server_name localhost;
return 301 https://$host$request_uri;
}
打开浏览器访问 https://localhost,安全锁图标 ✅。
2️⃣ VSCode Live Server
Live Server 本身支持 HTTPS 模式,可通过配置文件启用。
在项目根目录创建 .vscode/settings.json:
{
"liveServer.settings.https": {
"enable": true,
"cert": "/path/to/localhost+2.pem",
"key": "/path/to/localhost+2-key.pem"
}
}启动 Live Server 后,访问
https://127.0.0.1:5500/即可安全访问。
3️⃣ http-server(npm 工具)
安装:
npm i -g http-server
使用 mkcert 证书启动:
http-server -S -C localhost+2.pem -K localhost+2-key.pem -p 443<br>4️⃣ Node.js (Express / Koa / Fastify)
Node.js 原生 HTTP/2 + HTTPS 静态服务器
// server-http2.mjs
import fs from 'fs';
import http2 from 'http2';
import path from 'path';
import { fileURLToPath } from 'url';
import mime from 'mime-types';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const PORT = 8443;
const DIST_DIR = __dirname; // 脚本同级目录,可改为 ./dist
const options = {
key: fs.readFileSync('./localhost-key.pem'),
cert: fs.readFileSync('./localhost.pem'),
allowHTTP1: true
};
const server = http2.createSecureServer(options);
server.on('stream', (stream, headers) => {
try {
let reqPath = headers[':path'];
if (reqPath === '/') reqPath = '/index.html';
const filePath = path.normalize(path.join(DIST_DIR, decodeURIComponent(reqPath)));
if (!fs.existsSync(filePath) || !fs.statSync(filePath).isFile()) {
stream.respond({ ':status': 404 });
stream.end('Not Found');
return;
}
const contentType = mime.lookup(filePath) || 'application/octet-stream';
stream.respond({
':status': 200,
'content-type': contentType
});
stream.end(fs.readFileSync(filePath));
} catch (err) {
console.error(err);
stream.respond({ ':status': 500 });
stream.end('Internal Server Error');
}
});
server.listen(PORT, () => {
console.log(`🚀 HTTP/2 static server running at https://localhost:${PORT}`);
});
使用方式
- 将
server-http2.mjs、index.html、localhost.pem、localhost-key.pem放在同一目录。 - 安装依赖:
npm install mime-types
- 启动:
node server-http2.mjs
- 打开浏览器访问:
https://localhost:8443
✅ 此时 访问 https://localhost:8443,index.html 和所有静态资源均可加载,浏览器 Network 面板协议显示 h2。
Express 示例
import fs from 'fs';
import https from 'https';
import express from 'express';
const app = express();
app.get('/', (req, res) => {
res.send('Hello HTTPS + HTTP2!');
});
const options = {
key: fs.readFileSync('./localhost+2-key.pem'),
cert: fs.readFileSync('./localhost+2.pem')
};
https.createServer(options, app).listen(443, () => {
console.log('HTTPS server running at https://localhost');
});
Koa 示例
import Koa from 'koa';
import https from 'https';
import fs from 'fs';
const app = new Koa();
app.use(ctx => { ctx.body = 'Koa over HTTPS!'; });
https.createServer({
key: fs.readFileSync('./localhost+2-key.pem'),
cert: fs.readFileSync('./localhost+2.pem')
}, app.callback()).listen(443);
Fastify 示例
import Fastify from 'fastify';
import fs from 'fs';
const fastify = Fastify({
https: {
key: fs.readFileSync('./localhost+2-key.pem'),
cert: fs.readFileSync('./localhost+2.pem')
},
http2: true
});
fastify.get('/', async () => ({ hello: 'HTTP2' }));
fastify.listen({ port: 443 });
以上示例均支持 HTTP2 与 HTTPS,可直接在 Chrome 调试 Network 协议为
h2。
5️⃣ Vite 本地服务支持 HTTP/2 + HTTPS 实现
在使用 Vite 作为前端开发服务器时,默认它只启用基于 HTTP/1.1 的开发服务,
若希望支持更接近生产环境的 HTTP/2 + HTTPS 本地预览,
我们可以借助 Node.js 原生模块与 mkcert 生成的本地证书来扩展。
📁 目录结构
为避免影响现有开发配置,我们新建一个独立的 vite-server-http2.mjs 与扩展配置文件:
project-root/
├─ index.html
├─ vite.config.js # 原有 Vite 配置(保持不变)
├─ vite.h2.extend.js # 新增:HTTP/2 + HTTPS 扩展配置
├─ vite-server-http2.mjs # 新增:启动 HTTP/2 + HMR 服务脚本
├─ localhost+2.pem # mkcert 生成证书
└─ localhost+2-key.pem # mkcert 生成私钥
⚙️ vite.h2.extend.js
独立的 Vite 配置文件,用于在中间件模式下提供 HTTPS 与独立的 HMR WebSocket 服务。
import fs from 'fs';
import path from 'path';
import getPort from 'get-port';
export default async () => {
const hmrPort = await getPort({ port: 24678 });
console.log(`[vite] HMR WebSocket will use port ${hmrPort}`);
return {
server: {
middlewareMode: true,
https: {
key: fs.readFileSync(path.resolve('./localhost+2-key.pem')),
cert: fs.readFileSync(path.resolve('./localhost+2.pem')),
},
hmr: {
protocol: 'wss',
host: 'localhost',
port: hmrPort,
},
},
};
};
🧩 vite-server-http2.mjs
使用 Node.js 原生 http2.createSecureServer() 搭建开发服务,
支持动态端口检测、静态资源访问和热更新注入。
// vite-server-http2.mjs
import fs from 'fs';
import path from 'path';
import http2 from 'http2';
import os from 'os';
import getPort from 'get-port';
import { createServer as createViteServer } from 'vite';
// 获取所有非内网回环的 IPv4 地址(与 Vite 打印风格一致)
function getNetworkAddresses() {
const nets = os.networkInterfaces();
const addrs = new Set();
for (const name of Object.keys(nets)) {
for (const net of nets[name]) {
if (net.family === 'IPv4' && !net.internal) {
addrs.add(net.address);
}
}
}
return [...addrs];
}
async function startServer() {
// 首选端口(会自动查找空闲端口)
const preferredPort = 12443;
const port = await getPort({ port: preferredPort });
console.log(`\n🟢 Starting custom Vite HTTP/2 server on https://localhost:${port} ...\n`);
console.log(`🔑 Using certificate: ${path.resolve('./localhost+2.pem')}\n`);
// 创建 Vite 中间件(读取你提供的扩展配置)
const vite = await createViteServer({
configFile: './vite.h2.extend.js'
});
// http2 安全服务器,allowHTTP1 以兼容 http/1.1 客户端
const server = http2.createSecureServer(
{
key: fs.readFileSync(path.resolve('./localhost+2-key.pem')),
cert: fs.readFileSync(path.resolve('./localhost+2.pem')),
allowHTTP1: true
},
async (req, res) => {
try {
const url = req.url;
// 优先交给 Vite 中间件处理(模块、静态资源、HMR 路由等)
vite.middlewares(req, res, async (err) => {
if (err) {
vite.ssrFixStacktrace(err);
res.writeHead(500);
res.end(err.message);
return;
}
// 对于 HTML 请求,注入 transformIndexHtml
if (!url.includes('.') || url.endsWith('.html')) {
let html = fs.readFileSync(path.resolve('./index.html'), 'utf8');
html = await vite.transformIndexHtml(url, html);
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end(html);
}
// 其它请求已由 vite.middlewares 处理或交给下游返回 404
});
} catch (e) {
vite.ssrFixStacktrace(e);
res.writeHead(500);
res.end(e.message);
}
}
);
server.listen(port, () => {
// 打印 Local 与 Network(所有 IPv4)地址,风格与 Vite 一致
const networkAddrs = getNetworkAddresses();
console.log('✅ Vite HTTP/2 server is running!\n');
console.log(` ➜ Local: https://localhost:${port}/`);
if (networkAddrs.length) {
for (const addr of networkAddrs) {
console.log(` ➜ Network: https://${addr}:${port}/`);
}
} else {
console.log(` ➜ Network: https://<no-network-ip-found>:${port}/`);
}
// HMR 端口(vite.h2.extend.js 中生成的端口)
const hmrPort = vite.config?.server?.hmr?.port || 24678;
console.log(`\n 🔥 HMR WS: wss://localhost:${hmrPort}/\n`);
});
server.on('error', (err) => {
console.error('❌ Server error:', err);
});
}
startServer().catch((err) => {
console.error('❌ Failed to start Vite HTTP/2 server:', err);
process.exit(1);
});
🪄 package.json 脚本配置
在 scripts 中添加新的启动命令:
{
"scripts": {
"dev": "vite",
"dev:h2": "node vite-server-http2.mjs"
}
}
🚀 启动方式
执行命令:
npm run dev:h2
终端输出类似:
🟢 Starting custom Vite HTTP/2 server on https://localhost:12443 ...
🔑 Using certificate: E:\fork-project\code\gpaas_ui\localhost+2.pem
[vite] HMR WebSocket will use port 24678
✅ Vite HTTP/2 server is running!
➜ Local: https://localhost:12443/
➜ Network: https://192.168.96.60:12443/
🔥 HMR WS: wss://localhost:24678/
打开浏览器访问:
若首次访问提示证书不受信任,请先信任 mkcert 根证书即可。
注:如果希望本机网络IP支持https受信访问,通过终端输入 ipconfig 查看网路ip地址,生成证书需要添加本机网络ip地址如:
mkcert localhost 127.0.0.1 192.168.96.40✅ 功能总结
| 功能 | 描述 |
|---|---|
| ✅ HTTPS | 使用 mkcert 本地证书 |
| ✅ HTTP/2 | 基于 Node.js 原生 http2 模块 |
| ✅ 动态端口 | 自动检测端口冲突并递增 |
| ✅ HMR 热更新 | 独立 WSS 端口,支持 Vite 模块热替换 |
| ✅ 静态文件 | 从项目根目录读取并注入 HTML |
📘 小结
该方案的优势在于:
- 与原有 vite.config.js 完全解耦,不会干扰正常
npm run dev; - 可复用 mkcert 本地信任证书;
- 支持真实 HTTP/2 通信测试,更贴近生产环境;
- 仍然保留 HMR 热更新,开发体验不受影响。
六、在浏览器中验证证书生效
1. 打开浏览器访问,显示安全图标:
https://localhost
2. 点击证书信息,查看颁发机构为 mkcert,浏览器信任证书 ✅。
3. DevTools → Network → Protocol,查看协议为 h2,说明 HTTP/2 生效。
七、进阶:多个项目共享证书
可以在全局统一证书目录:
mkdir -p ~/.mkcert
cd ~/.mkcert
mkcert localhost 127.0.0.1
在各项目配置时统一引用:
ssl_certificate ~/.mkcert/localhost.pem;
ssl_certificate_key ~/.mkcert/localhost-key.pem;
八、mkcert 常见问题
| 问题 | 原因 | 解决办法 |
|---|---|---|
| 浏览器仍提示“不安全” | 根证书未被信任 | 重新执行 mkcert -install |
| Firefox 不识别 | 需单独导入到 Firefox 信任库 | 执行 mkcert -install 后重启 Firefox |
| VSCode Live Server 报错 | 路径错误或端口被占用 | 检查文件路径与端口占用情况 |
| Node 启动失败 | 权限问题 | 尝试使用 sudo 或非 443 端口 |
九、总结
使用 mkcert,可以轻松让本地开发环境支持 HTTPS 与 HTTP/2,避免安全策略限制,完美模拟线上环境。
配合 Nginx、Node.js、VSCode Live Server 等多种服务,你可以在本地构建高保真、安全的开发体验。
🧩 结语
HTTPS 已成为前端开发的基础环境要求,而 mkcert 让我们摆脱复杂的证书配置,像写代码一样优雅地构建安全环境。
在 AI、WebRTC、PWA 时代,安全上下文是新一代 Web 的起点。动手启用 HTTPS 吧,让你的本地环境更接近未来。
文章评论