用 Caddy 为本地开发服务自动配置 HTTPS 证书
起因
本地开发的时候,HTTP 其实大部分时候够用,但总有几个东西卡在 http://localhost 上不给面子。比如 Service Worker,比如 navigator.clipboard,比如某些需要 Secure Context 的 Web API,再比如后端设了 SameSite=None 的 Cookie,到了本地就直接被浏览器丢掉。每次碰到这种问题,都得折腾一遍证书,烦得很。
之前我一直是用 mkcert 自签一套,然后手动往系统钥匙串里导入根证书。能用,但是换台机器就得重来一遍,而且自签证书在浏览器里偶尔还是会跳个红色警告,看着心里不踏实。后来索性换成 Caddy,让它自己管证书,省事不少。
为什么不用 mkcert 了
mkcert 本身没啥问题,就是流程有点碎:先生成根证书,再 mkcert -install,再给每个域名签一张,签完还得保证路径对。项目一多,证书文件散落在各个目录,过两个月自己都忘了哪张是哪张。
Caddy 的思路不太一样,它内置了一个本地 CA,你只要在配置里写上域名,它自己签发、自己续期、自己装到系统信任区。你不需要知道证书放在哪,也不需要关心有效期,反正它会在后台默默处理。
安装
macOS 直接 brew install caddy。Ubuntu/Debian 建议走官方 apt 源,别用系统自带的那个老版本,功能会缺。Windows 用 scoop 或者 choco 都行,或者直接下个 exe 扔到 PATH 里。
装完敲一下 caddy version,能出版本号就说明没问题。
写一个最小的 Caddyfile
在项目根目录建一个 Caddyfile,内容大概长这样:
{
local_certs
}
app.localhost {
reverse_proxy 127.0.0.1:5173
}
api.localhost {
reverse_proxy 127.0.0.1:8080
}
local_certs 这个全局选项是告诉 Caddy,所有站点都用内部 CA 签发,别去走 ACME。其实对于 .localhost 结尾的域名,Caddy 默认就会用内部 CA,但显式写出来更保险,也不会因为某些版本的行为变化而翻车。
reverse_proxy 后面写你实际的服务地址。前端 Vite 默认 5173,后端随便,改成你自己的。
然后 caddy run 前台跑一下看看有没有报错,没问题的话可以用 caddy start 让它后台待着。改完配置不用重启,caddy reload 就行。
让域名解析到本机
.localhost 这个顶级域在 Chrome 和 Firefox 里是硬编码解析到 127.0.0.1 的,所以理论上不用改 hosts。但 Safari 不吃这一套,某些 Linux 发行版的 glibc 也不一定认。稳妥起见,还是往 hosts 里加两行:
127.0.0.1 app.localhost
127.0.0.1 api.localhost
macOS/Linux 是 /etc/hosts,Windows 是 C:\Windows\System32\drivers\etc\hosts,改完记得刷新一下 DNS 缓存,不然你以为没生效。
如果你不想动 hosts,也可以用 nip.io 或者 sslip.io 这类通配 DNS,比如 app.127.0.0.1.nip.io,解析出来就是 127.0.0.1,缺点是要联网,断网就废了。
信任根证书(关键一步)
Caddy 的本地 CA 默认是不被系统信任的,你得手动装一次:
caddy trust
这条命令会把 Caddy 的根证书塞进系统信任区,macOS 会弹窗让你输密码,Linux 走 update-ca-certificates,Windows 会弹 UAC。装完以后,浏览器地址栏那个小锁就是真的绿的了。
这里有个坑:Firefox 有自己独立的证书库,不读系统的。要么在 about:config 里把 security.enterprise_roots.enabled 改成 true,让它读系统证书;要么手动把 Caddy 的根证书导入 Firefox 的设置里。根证书一般在 ~/.local/share/caddy/pki/authorities/local/root.crt,macOS 是 ~/Library/Application Support/Caddy/pki/authorities/local/root.crt。
几个我踩过的坑
第一个是端口。Caddy 默认监听 80 和 443,如果本机有 nginx、Apache 或者某些会抢端口的软件,就会起不来。要么把那些服务停掉,要么在 Caddyfile 里给站点指定别的端口,比如 app.localhost:8443,访问的时候带上端口号就行。
第二个是权限。非 root 用户绑定 443 会失败,Linux 上可以给 caddy 二进制加个 capability:
sudo setcap cap_net_bind_service=+ep $(which caddy)
macOS 上则老老实实 sudo caddy run,或者用 launchd 跑。
第三个是 HSTS。如果你之前在这个域名上跑过 HTTPS 并且设了 HSTS,浏览器会强制走 HTTPS,这时候 Caddy 一停,页面就直接打不开了,连报错都看不到。碰到这种情况清一下浏览器的 HSTS 记录,Chrome 在 chrome://net-internals/#hsts。
第四个是 Vite 的热更新。走 HTTPS 之后,HMR 的 WebSocket 有时候连不上,得在 vite.config.ts 里配一下 server.hmr.clientPort,或者干脆用 server.hmr.protocol = 'wss'。这个问题跟 Caddy 没关系,纯粹是 Vite 自己的配置问题。
最后
整套流程跑通之后,本地开发的体验会顺畅很多。不用再记证书路径,不用再手动续期,新加一个服务只需要往 Caddyfile 里多写三行,然后 caddy reload。改完配置记得 reload,不然你会盯着浏览器缓存怀疑人生,白白 debug 半小时。
评论
还没有评论。
发表评论
提交后评论将经过自动审核,审核通过后公开展示。