現代 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 獲取真實證書。你的程式碼應該從環境變數讀取證書路徑,這樣切換隻需更改配置。