Caddy 2 改完 Caddyfile 直接 systemctl reload caddy,是很多人固定下来的动作。其实 Caddy 自带 caddy validate,而且它做的事比「语法检查」要多一层:会真的把所有模块 provision 一遍。搞清楚 adapt、validate、reload 三者的边界,能省掉两类时间——「明明验证通过却起不来」,以及「明明配置没问题,验证却报错」。
一、一条命令起步
# 最常用写法:验证 /etc/caddy/Caddyfile
sudo caddy validate --config /etc/caddy/Caddyfile
# 文件名不是 Caddyfile* 时,必须显式指定适配器
sudo caddy validate --config /etc/caddy/caddy.conf --adapter caddyfile
# 从标准输入读
cat Caddyfile | caddy validate --config -
# 配置里用了 {$VAR} 占位符时,把环境变量文件一起带上
sudo caddy validate --config /etc/caddy/Caddyfile --envfile /etc/caddy/caddy.env
# 看结果:0 表示通过
echo $?caddy validate 只有三个参数:
| 参数 | 说明 |
|---|---|
-c, --config <path> | 要验证的配置文件。不写时默认用当前目录下的 Caddyfile;写 - 表示从 stdin 读 |
-a, --adapter <name> | 配置适配器名。文件名以 Caddyfile 开头或以 .caddyfile 结尾时会自动按 caddyfile 适配;否则不写这个参数就会按原生 JSON 解析 |
--envfile <file> | 按 KEY=VALUE 加载环境变量文件,支持 # 注释、export 前缀、双引号和多行值 |
文件名规则值得单独记一下:把生产配置叫 Caddyfile.prod 依然会被当成 Caddyfile 处理(前缀匹配),而叫 caddy.conf 就必须补 --adapter caddyfile,否则报的是 JSON 语法错(类似 invalid character '/'),很容易看错方向。
二、adapt、validate、reload 到底差在哪
| 命令 | 做了什么 | 能发现的错误 |
|---|---|---|
caddy fmt --diff Caddyfile | 只做格式化对比,不解析语义 | 缩进、空格等格式问题(有差异时退出码为 1) |
caddy adapt -c Caddyfile --pretty | 把 Caddyfile 解析、适配成 JSON 并打印 | 语法错误、未知指令、参数个数不对 |
caddy adapt -c Caddyfile --validate | 适配后再加载并 provision 一遍 | 上面这些,加上 provision 阶段的错误 |
caddy validate -c Caddyfile | 直接反序列化 + 加载 + provision,但不启动 | 同上一行(省掉「顺便看 JSON」) |
caddy reload -c Caddyfile | 对正在运行的实例加载并 provision 新配置 | 同上,且不与运行实例抢资源 |
官方文档里给了一个很能说明问题的例子。这份 Caddyfile:
localhost
tls cert_notexist.pem key_notexist.pem先适配,成功:
caddy adapt --config Caddyfile加上 --validate,就失败了:
caddy adapt --config Caddyfile --validate
adapt: validation: loading app modules: module name 'tls':
provision tls: loading certificates: open cert_notexist.pem: no such file or directory原因是证书文件要到 provision 阶段才真正被读取。「adapt 通过」只代表语法没问题,不代表这份配置能跑——这就是为什么验证要用 validate 而不是 adapt。
三、对运行中的实例,别用 validate,用 reload
官方文档专门写了一段提醒:因为模块是真的被 provision,某些模块在这一步会独占资源,第二个 Caddy 进程拿不到。最典型的是 acme_server 指令——它要打开一个同一时刻只能被一个进程打开的数据库,所以 Caddy 正在运行时去 validate 这份配置会报数据库超时,哪怕配置本身完全正确。
# 已经有一个 Caddy 在跑:用 reload,它在运行中的进程里 provision 新配置
sudo caddy reload --config /etc/caddy/Caddyfile
# 配置内容没变也想强制重新 provision(例如重载手动放入的证书)时用 --force
sudo caddy reload --config /etc/caddy/Caddyfile --forcecaddy reload 等价于向管理 API 的 /load 端点 POST 一份配置,所以管理端点不能关。它的一个重要性质是失败不影响现状:provision 失败时当前配置继续服务,成功才切换。这也是「改配置别 stop / restart」的原因——那两个动作会中断现有连接,而 reload 不会。
四、validate 查不出来的东西
验证通过不等于能正常对外服务,下面这些只有运行时才暴露:
- 端口占用:
listen tcp :80: bind: address already in use是启动阶段的事。 - DNS 解析、80/443 是否可达、ACME 能否成功签发证书。
- 反向代理的后端是否活着——upstream 不健康,配置照样是合法的。
- 占位符实际取到的值:Caddyfile 里的
{$DOMAIN}在验证时用的是你当前 shell 的环境变量或--envfile,和 systemd 里跑时的环境可能不同。 - 相对路径:Caddyfile 里的相对路径按进程的工作目录解析。在
/root下验证通过,不代表 systemd 从别的工作目录启动时也通过。生产配置里统一写绝对路径最稳。
五、常见报错与处理
| 报错 | 原因 | 处理 |
|---|---|---|
unrecognized directive: xxx | 指令拼错,或者该指令来自第三方模块(DNS 提供商、layer4 等),当前二进制里没编进去 | caddy list-modules 查一下;确实缺模块就用 xcaddy 重新构建二进制 |
unrecognized global option | 全局选项写错位置或写错名字 | 全局选项必须放在文件最前面的 { } 块里 |
provision tls: loading certificates: open xxx.pem: no such file or directory | 证书路径不对、文件不存在,或运行用户没有读权限 | 先确认路径,再用服务用户跑一遍验证(见下一条) |
| 数据库超时 / locked | 配置里有 acme_server 之类会独占资源的模块,而 Caddy 已经在运行 | 改用 caddy reload |
permission denied(storage 目录) | 用 root 验证通过,但服务是以 caddy 用户跑的 | 用同样的身份验证:sudo -u caddy caddy validate -c /etc/caddy/Caddyfile |
invalid character '/' looking for beginning of value | 把 Caddyfile 当原生 JSON 解析了 | 补 --adapter caddyfile,或把文件名改成 Caddyfile* |
| 占位符被替换成空值引发的怪错(如域名不合法) | 验证时环境变量没加载 | 加 --envfile,或先 export 再验证 |
六、放进日常流程
# 1. 改完配置先格式化(--diff 只对比,有差异退出码 1,适合当 CI 卡口)
sudo caddy fmt --overwrite /etc/caddy/Caddyfile
# 2. 验证:0 才继续
sudo caddy validate --config /etc/caddy/Caddyfile
# 3. 应用到运行中的实例(不中断连接)
sudo systemctl reload caddy
# 等价于 sudo caddy reload --config /etc/caddy/Caddyfile --force
# 4. 观察日志
sudo journalctl -u caddy -f用容器的话,注意镜像的 entrypoint 已经是 caddy,所以命令行里不要再写一遍:
# 本地挂载一份配置进去验证
docker run --rm -v "$PWD/Caddyfile:/etc/caddy/Caddyfile:ro" \
caddy:2 validate --config /etc/caddy/Caddyfile
# 验证 compose 里正在运行的容器所用配置(在容器内执行,路径以容器内为准)
docker compose exec caddy caddy validate --config /etc/caddy/CaddyfileCI 里可以直接把这两条当门禁:caddy fmt --diff Caddyfile 和 caddy validate --config Caddyfile,任一非 0 退出就中断流水线,配置错误就不会合进主分支了。
七、速查卡
# 验证:退出码 0 表示配置可加载、可 provision
caddy validate --config /etc/caddy/Caddyfile --adapter caddyfile
# 只看语法/适配结果(检查 Caddyfile 被翻译成了什么 JSON)
caddy adapt --config Caddyfile --pretty
# 适配 + provision 检查(比只看 JSON 强,比 validate 多一步输出)
caddy adapt --config Caddyfile --validate
# 对运行中的实例应用配置(失败则维持旧配置)
caddy reload --config /etc/caddy/Caddyfile --force
# 格式检查(有差异退出码 1)
caddy fmt --diff Caddyfile
# 排查「指令不认识」是不是模块缺失
caddy list-modules | grep -i 关键词一句话总结:语法问题用 caddy adapt 就能看出来,能不能跑起来必须用 caddy validate(它会真的 provision,比适配强一个层次);对已经在运行的实例改用 caddy reload,别用 validate 抢资源、也别 restart 断连接。
评论区