问题描述#
欸,最近在做小三月的即时聊天系统前端时,账号密码提交这一步忽然提醒了咱:就算是本地开发,也不能总把安全这件事当成"上线之后再说"嘛。
前端平时用 npm run dev 跑在 127.0.0.1:5173,访问起来确实很方便。但如果要模拟更接近线上环境的 HTTPS,或者让浏览器在本地也走一套正式一点的 TLS 流程,就可以把之前服务器上用过的 Caddy 搬到 macOS 本地来。
本地也配上 HTTPS,开发时心里就踏实一点啦。
这篇笔记记录的是:用 Homebrew 安装 Caddy,然后让 dev.localhost 反向代理到 Vite 的开发服务。
1. 启动前端开发服务#
先在前端项目里启动开发服务:
npm run dev如果是 Vite 项目,默认通常会监听在:
http://127.0.0.1:5173后面 Caddy 要做的事,就是把一个带 TLS 的本地域名转发到这个地址。
2. 安装 Caddy#
macOS 上直接用 Homebrew 安装即可:
brew install caddy安装完成后,可以先确认一下版本:
caddy version3. 编写 Caddyfile#
Homebrew 安装的 Caddy 默认配置文件可以放在:
/opt/homebrew/etc/Caddyfile编辑这个文件,写入下面的配置:
dev.localhost {
reverse_proxy 127.0.0.1:5173
}这里的意思很简单:浏览器访问 dev.localhost 时,由 Caddy 接住请求,再转发给本地的 127.0.0.1:5173。
这里最容易写错端口,咱可是盯着看了好几眼。
localhost 相关域名在本地开发里很方便,dev.localhost 不需要额外去公网 DNS 配解析;浏览器会把它当成本机地址使用。
4. 格式化配置#
写完配置之后,用 Caddy 自带的格式化命令整理一下:
caddy fmt --overwrite /opt/homebrew/etc/Caddyfile格式化后再运行,能少踩一些因为缩进、空格或者配置块格式带来的小坑。
5. 查看 Homebrew 服务#
如果想确认 Caddy 是否已经作为 Homebrew 服务存在,可以查看服务列表:
brew services list这里能看到 Caddy 当前是 started、stopped,还是没有通过 Homebrew service 方式启动。
6. 运行 Caddy#
开发阶段可以直接指定配置文件运行:
caddy run --config /opt/homebrew/etc/Caddyfile运行后,用浏览器访问:
https://dev.localhost如果前端的 npm run dev 还在跑,Caddy 就会把 HTTPS 请求代理到 127.0.0.1:5173。
7. 日志解读#
第一次运行 Caddy 时,终端里可能会刷出一串日志。哎呀,看起来密密麻麻的,但只要抓住关键几行就行。
如果看到类似下面的内容,整体就是正常启动成功:
serving initial configuration
certificate obtained successfully {"identifier": "dev.localhost", "issuer": "local"}这说明 Caddy 已经成功给 dev.localhost 签发了本地证书,并且开始按当前配置提供服务。这个时候就可以打开:
https://dev.localhost前提还是那句:Vite 服务也要先跑起来。
npm run dev终端里能看到类似下面的地址,就说明前端开发服务已经准备好了:
Local: http://localhost:5173/
看到 warning 先别急,咱一行一行看,很多其实只是提醒。
Caddyfile 没格式化#
WARN Caddyfile input is not formatted; run 'caddy fmt --overwrite'这个只是格式提醒,不影响运行。可以执行:
caddy fmt --overwrite /opt/homebrew/etc/Caddyfile然后重新运行:
caddy run --config /opt/homebrew/etc/Caddyfile自动开启 HTTPS#
http.auto_https enabling automatic HTTP->HTTPS redirects这是好事,意思是 Caddy 会自动把 HTTP 请求跳转到 HTTPS。所以访问:
http://dev.localhost可能会自动跳到:
https://dev.localhost本地证书已经被系统信任#
root certificate is already trusted by system这说明之前的本地证书信任已经生效了。换句话说,Caddy 的本地 CA 已经被系统认可,不用每次都重新处理证书信任。
HTTP/2 skipped because it requires TLS#
HTTP/2 skipped because it requires TLS {"addr": ":80"}
HTTP/3 skipped because it requires TLS {"addr": ":80"}这几行是在说 :80 是普通 HTTP 端口,不在这个端口上跑需要 TLS 的 HTTP/2 或 HTTP/3。正常,不是错误。
真正的 HTTPS 服务看这行:
server running {"name": "srv0", "protocols": ["h1", "h2", "h3"]}它表示 :443 上的服务已经跑起来了。
OCSP warning 可以忽略#
WARN tls stapling OCSP {"identifiers": ["dev.localhost"]}本地证书是 Caddy 自己签的,不是公网 CA 证书,所以 OCSP stapling 这种公网证书状态检查在本地开发里意义不大,可以忽略。
所以最终使用时,可以开两个终端。
第一个跑前端:
npm run dev第二个跑 Caddy:
caddy run --config /opt/homebrew/etc/Caddyfile然后浏览器打开:
https://dev.localhost如果能看到 Vite 页面,就说明本地 HTTPS 反向代理成功了。
8. Caddy 自动 TLS 的原理#
Caddy 配置 TLS 证书的核心特点是:默认自动 HTTPS。很多时候不需要手动写证书路径,只要把站点地址写清楚,Caddy 就会根据这个地址判断该用哪种证书策略。
咱可以把它理解成三种情况。
公网域名:自动申请 Let’s Encrypt / ZeroSSL 证书#
如果 Caddyfile 写的是公网域名:
muzimi.org {
reverse_proxy 127.0.0.1:3000
}只要满足这些条件:
域名 muzimi.org 正确解析到这台服务器
80 / 443 端口能被公网访问
Caddy 有权限监听 80 / 443Caddy 大致会走这样的流程:
启动 Caddy
↓
发现站点是 muzimi.org
↓
判断这是公网域名
↓
向 Let's Encrypt / ZeroSSL 这类 ACME CA 申请证书
↓
完成 ACME 验证
↓
保存证书和私钥
↓
自动启用 HTTPS
↓
证书快过期时自动续期所以公网域名最简单的反代配置通常就是:
example.com {
reverse_proxy 127.0.0.1:8080
}Caddy 会自动提供:
https://example.com并且把:
http://example.com重定向到:
https://example.com本地域名:使用 Caddy 本地 CA 签证书#
这次本地开发用的是:
dev.localhost {
reverse_proxy 127.0.0.1:5173
}dev.localhost 不是公网域名,Let’s Encrypt 不会给它签公网可信证书。所以 Caddy 会走另一套逻辑:
启动 Caddy
↓
发现域名是 dev.localhost / localhost
↓
无法使用公网 CA
↓
使用 Caddy 自己的本地 CA
↓
本地 CA 签发 dev.localhost 证书
↓
浏览器验证这个证书你看到的证书链大概就是:
Caddy Local Authority - 2026 ECC Root
└── Caddy Local Authority - ECC Intermediate
└── dev.localhost / localhost这里的重点是:Caddy 自己当了一个本地证书颁发机构 CA。
但是浏览器默认不一定信任这个本地 CA,所以需要执行:
caddy trust执行后,Caddy 会把自己的本地根证书加入 macOS 系统信任区。这样 Safari、Chrome、Atlas 等浏览器就会信任它签出来的本地证书。
本地开发时,常见配置就保持这样:
dev.localhost {
reverse_proxy 127.0.0.1:5173
}然后访问:
https://dev.localhost手动指定证书:tls cert key#
如果已经有自己的证书文件,比如:
/path/to/fullchain.pem
/path/to/privkey.pem也可以手动告诉 Caddy 使用它们:
example.com {
tls /path/to/fullchain.pem /path/to/privkey.pem
reverse_proxy 127.0.0.1:8080
}这种方式下,Caddy 会直接使用指定证书文件,而不是为这个站点自动申请证书。不过一般情况下,不建议一上来就手动配证书;Caddy 的优势正是自动申请、自动续期。
Caddyfile 地址会影响 TLS 行为#
如果站点地址写成普通域名:
example.com {
respond "hello"
}Caddy 会自动启用 HTTPS:
https://example.com如果写成本地域名:
localhost {
respond "hello"
}Caddy 会用本地 CA 自动签证书:
https://localhost如果写 IP 地址:
127.0.0.1 {
respond "hello"
}也可能走本地证书逻辑。不过本地开发更推荐用:
localhost或者:
dev.localhost如果显式写 http://:
http://localhost:8080 {
respond "hello"
}这就是明确告诉 Caddy:
我不要 HTTPS,只监听 HTTP这时不会自动启用 TLS。
如果显式写 https://:
https://dev.localhost {
reverse_proxy 127.0.0.1:5173
}这会明确启用 HTTPS。其实对于这篇文章的配置来说,写成下面这样也会自动启用 HTTPS:
dev.localhost {
reverse_proxy 127.0.0.1:5173
}这次配置实际发生了什么#
这次配置大概率是:
dev.localhost {
reverse_proxy 127.0.0.1:5173
}Caddy 启动后,如果日志里出现:
enabling automatic TLS certificate management {"domains": ["dev.localhost"]}意思就是:
Caddy 发现 dev.localhost 需要 TLS,于是启用自动证书管理。然后看到:
certificate obtained successfully {"identifier": "dev.localhost", "issuer": "local"}意思就是:
Caddy 已经成功给 dev.localhost 签发证书,签发者是 local,也就是 Caddy 本地 CA。如果是公网域名,日志里的 issuer 可能会变成 Let’s Encrypt 或 ZeroSSL 相关地址。
Caddy 的证书保存在哪里#
这次是直接运行:
caddy run --config /opt/homebrew/etc/Caddyfile如果日志里出现:
/Users/firefly/Library/Application Support/Caddy那当前证书、本地 CA 和缓存数据大概率就在:
~/Library/Application\ Support/Caddy如果改用 Homebrew service 方式运行:
brew services start caddyHomebrew 可能会用自己的运行用户和环境变量,例如把 HOME 或 XDG_DATA_HOME 指向 Homebrew 管理目录。这样 Caddy 的数据目录就可能变成:
/opt/homebrew/var/lib/caddy或者相关的 Homebrew 管理路径。
所以要注意:直接 caddy run 和 brew services start caddy 可能使用不同的数据目录。这会影响证书、本地 CA 和缓存的位置;遇到证书信任或证书重复生成问题时,可以先看 Caddy 启动日志里打印的数据目录。
最常用配置总结#
本地 Vite HTTPS:
dev.localhost {
reverse_proxy 127.0.0.1:5173
}公网网站反代:
example.com {
reverse_proxy 127.0.0.1:3000
}静态网站:
example.com {
root * /var/www/example
file_server
}手动证书:
example.com {
tls /path/to/fullchain.pem /path/to/privkey.pem
reverse_proxy 127.0.0.1:3000
}禁用 HTTPS,只跑 HTTP:
http://localhost:8080 {
respond "hello"
}这块咱最后记一句就够:Caddy 不是先让你配置证书,而是先让你写域名;它会根据域名自动决定用公网 ACME 证书,还是本地 CA 证书。
9. 使用时的注意点#
npm run dev和caddy run都要保持运行,一个负责前端开发服务,一个负责 HTTPS 入口。- 如果改了 Caddyfile,需要重新运行或 reload Caddy,才能让新配置生效。
- 如果浏览器第一次访问时提示证书信任相关信息,要按系统提示处理本地 CA 信任问题。
- 生产环境的证书和域名解析要按真实公网域名配置,本地的
dev.localhost只适合开发调试。
总结#
这套配置的核心链路就是:
https://dev.localhost -> Caddy -> http://127.0.0.1:5173前端继续用熟悉的 npm run dev,浏览器这边则可以用 HTTPS 访问本地页面。这样在调试登录、加密提交、Cookie 安全属性,或者某些只在安全上下文里可用的浏览器能力时,都更接近真实环境。
嘿嘿,本地 HTTPS 搞定!这张照片咱得收进开发笔记里。
好啦,这次的 macOS 本地 TLS 小笔记就到这里。下次再遇到"本地和线上行为不一致"这种事,咱至少又多了一件趁手的小工具啦。