个人站从备案到 HTTPS:Caddy + Let's Encrypt 的三个坑
给一个个人项目配域名和证书,本来以为是一小时的事。实际花的时间多得多,因为每个故障的表现都在误导我。
坑一:页面打不开,问题不在页面
最开始的症状是"打开是一片空白/超时"。我第一反应是去查页面内容、查服务有没有起。
真正的原因:只有 api 这个子域配了 A 记录,主域 @ 和 www 根本没解析。浏览器拿到 NXDOMAIN,连服务器都没连上。
dig 一条命令就能排除:
dig +short example.com
dig +short www.example.com
dig +short api.example.com
前两条空、第三条有 IP —— 答案就在这儿。
教训:任何"打不开"的问题,先确认 DNS,再怀疑一切。
顺带一个细节:域名服务商的"关联已有网站"这类功能,有时候会顺手帮你写一条根域的 A 记录,但不一定写 www。所以两个都要单独确认,不要假定它们一起生效。
坑二:一个域名解析不了,整个站点块的证书都签不出来
Caddy 的配置里,同一个站点块可以写多个域名:
example.com, www.example.com {
root * /var/www
file_server
}
这看起来很自然。但如果 www 那一刻还没解析,Let's Encrypt 的 HTTP-01 校验会拿到 NXDOMAIN,整个块的证书申请直接失败——包括本来完全正常的 example.com。
失败之后更麻烦:Caddy 会进入指数退避,短时间内不再尝试。这时候 systemctl reload caddy 是没用的,配置重载了,但退避状态还在。必须:
sudo systemctl restart caddy
这个区别我卡了很久:reload 看起来"生效了",日志里却一直不签发。
修法是在部署脚本里按实际解析情况决定包含哪些域名,而不是写死:
if getent hosts www.example.com >/dev/null 2>&1; then
HOSTS="example.com, www.example.com"
else
HOSTS="example.com"
echo "www 未解析,本次只绑主域"
fi
宁可少绑一个域名,也不要把整个块的签发拖死。
坑三:路径多一级,全站 404
Caddy 的 root 写错一级,所有静态资源都 404,但服务本身、证书、反向代理全部正常。
我的是:
handle_path /updates/* {
root * /opt/app/deploy/updates # 少了 deploy/ 这一级 → 全站 404
file_server
}
handle_path 会剥掉 /updates 前缀,所以 root 要给到"剥掉之后仍能拼出正确路径"的那一层。写的时候心里要清楚:剥掉前缀之后,剩下的是 /manifest.json,拼在哪。
改配置前先备份,改完先校验再 reload:
sudo cp /etc/caddy/Caddyfile /etc/caddy/Caddyfile.bak-$(date +%Y%m%d-%H%M%S)
sudo caddy validate --config /etc/caddy/Caddyfile --adapter caddyfile
sudo systemctl reload caddy
validate 通过不代表路由对,但至少能挡住语法错误把整个站点搞挂。
反向代理:白名单比全放更省事
一开始我把 /api/* 整个反代到后端,省事。后来改成显式白名单:
@proxy path /api/health /api/register /api/login /api/logout /api/profile
handle @proxy {
request_body {
max_size 32KB
}
reverse_proxy 127.0.0.1:8766
}
@api path /api/*
handle @api {
respond "Not Found" 404
}
好处有三个:
- 后端以后新增了没打算公开的接口,不会被顺带暴露
- 管理类接口不需要额外的鉴权就天然不可达
max_size顺手挡住了超大请求
白名单的代价是每加一个接口要改两处(Caddy 和代码)。这个成本我接受,因为它强制我在"要不要公开"这件事上做一次明确决定。
多入口共用一份站点
三个域名(主域、www、api 子域)指向同一个目录、同一套路由,用 Caddy 的 snippet 复用:
(site) {
# ... 所有路由规则
}
api.example.com {
import site
}
example.com, www.example.com {
import site
}
这样维护的永远只有一份配置。之前我担心"三个域名要维护三份",实际完全不用——域名和站点是两个层次的东西。
小结
三个坑的共同点:症状和原因隔着一层。页面打不开是因为 DNS,证书签不出是因为另一个域名,404 是因为路径层级。
所以现在我的排查顺序固定成:DNS → 证书 → 配置语法 → 路由 → 应用。逐层往下,不在最上面那层猜。