现代 Web API(Service Workers、地理位置、剪贴板、摄像头)要求 HTTPS——即使在开发环境中也是如此。本指南介绍如何在 localhost 上获得受信任的 HTTPS 而不会出现浏览器警告。
为什么 Let’s Encrypt 无法用于 localhost
Let’s Encrypt 通过检查服务器上的文件或 DNS 记录来验证域名所有权。localhost 不是一个真实域名——它只在你的机器上解析为 127.0.0.1。没有人能验证你”拥有” localhost,因此没有公共证书颁发机构会为其签发证书。
你的 localhost HTTPS 选项:
- mkcert — 创建本地信任的证书(推荐)
- 自签名证书 — 可以用但会显示浏览器警告
- 隧道服务 — ngrok、Cloudflare Tunnel(真实域名,真实证书)
方案一:mkcert(推荐)
mkcert 在你的机器上创建一个本地证书颁发机构,将其添加到系统信任存储,然后签发由该 CA 签名的证书。浏览器信任这些证书,无任何警告。
安装 mkcert
# macOS
brew install mkcert
# Linux (requires libnss3-tools for Firefox)
sudo apt install libnss3-tools
brew install mkcert # or download from GitHub releases
# Windows
choco install mkcert
# or: scoop install mkcert
设置本地 CA
mkcert -install
# Output: Created a new local CA
# The local CA is now installed in the system trust store
这会将根证书添加到你的系统和浏览器信任存储。只需执行一次——它对所有未来的证书都有效。
生成证书
# For localhost
mkcert localhost 127.0.0.1 ::1
# For a custom local domain
mkcert myapp.test "*.myapp.test" localhost 127.0.0.1
# Output:
# Created a new certificate valid for the following names:
# - "localhost"
# - "127.0.0.1"
# - "::1"
# The certificate is at "./localhost+2.pem" and the key at "./localhost+2-key.pem"
在开发服务器中使用
Node.js/Express:
const https = require('https');
const fs = require('fs');
const app = require('./app');
https.createServer({
key: fs.readFileSync('./localhost+2-key.pem'),
cert: fs.readFileSync('./localhost+2.pem'),
}, app).listen(3000);
Nginx(本地):
server {
listen 443 ssl;
server_name localhost;
ssl_certificate /path/to/localhost+2.pem;
ssl_certificate_key /path/to/localhost+2-key.pem;
# ...
}
Vite / webpack dev server:
// vite.config.js
import fs from 'fs';
export default {
server: {
https: {
key: fs.readFileSync('./localhost+2-key.pem'),
cert: fs.readFileSync('./localhost+2.pem'),
},
},
};
安全警告
永远不要分享 mkcert 根 CA 密钥(mkcert -CAROOT 目录中的 rootCA-key.pem)。任何拥有它的人都可以为你机器上的任何域名创建受信任的证书——本质上是本地中间人攻击能力。
mkcert 仅用于开发。生产环境请使用 Let’s Encrypt。
方案二:自签名证书
如果无法安装 mkcert,用 OpenSSL 生成自签名证书:
openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:P-256 \
-keyout localhost-key.pem -out localhost-cert.pem \
-days 365 -nodes -subj "/CN=localhost" \
-addext "subjectAltName=DNS:localhost,IP:127.0.0.1"
缺点: 浏览器会显示安全警告,因为自签名证书不被任何证书颁发机构信任。你需要每次都点击通过警告(或添加例外)。
方案三:隧道(真实域名 + 真实证书)
使用隧道服务将本地服务器暴露为真实域名和证书:
# ngrok
ngrok http 3000
# Gives you: https://abc123.ngrok.io
# Cloudflare Tunnel
cloudflared tunnel --url http://localhost:3000
# Gives you: https://random-name.trycloudflare.com
适用场景: 测试 Webhook、与队友共享、测试需要 HTTPS 的 OAuth 回调。
mkcert vs 自签名 vs 隧道
| mkcert | 自签名 | 隧道 | |
|---|---|---|---|
| 浏览器警告 | 无 | 有(每次会话) | 无 |
| 配置难度 | 低(一次安装) | 低(一条命令) | 低 |
| 离线可用 | ✅ | ✅ | ❌ |
| 性能 | 本地速度 | 本地速度 | 网络延迟 |
| 自定义域名 | ✅(*.test 等) | ✅ | 提供商分配 |
| 最适合 | 日常开发 | 快速一次性使用 | Webhook、共享 |
常见问题
可以在团队中共享同一个 mkcert 证书吗?
不要共享根 CA 密钥。每个开发者应该在自己的机器上运行 mkcert -install 来创建自己的本地 CA。你可以共享生成的证书(.pem 文件),但每个人都需要安装 CA 才能信任它们。
mkcert 适用于 Docker 吗?
适用。将证书文件挂载到容器中,在服务器配置中引用它们。容器本身不需要安装 mkcert——只需要 .pem 文件。你的主机(浏览器运行的地方)需要安装 mkcert CA。
应该使用哪个本地域名?
使用 .test(如 myapp.test)——它被 IETF 保留用于测试,永远不会与真实域名冲突。避免使用 .dev(Google 拥有)和 .local(被 mDNS 使用)。在 /etc/hosts 中添加条目将你的 .test 域名指向 127.0.0.1。
如何从 localhost HTTPS 切换到生产 HTTPS?
它们完全独立。你的 localhost 证书(mkcert/自签名)留在开发机器上。生产环境从 Let’s Encrypt 通过 GetHTTPS 获取真实证书。你的代码应该从环境变量读取证书路径,这样切换只需更改配置。