Caddy 可以自动申请和续期 HTTPS 证书。自动 HTTPS 依赖域名配置、DNS、ACME 验证入口和证书存储;任何一项不可用,签发或续期都会失败。容器已经启动,只能说明 Caddy 进程正在运行。

Caddy 从读取域名、完成 ACME 验证、保存证书到后台续期和外部监测的流程图
签发后,证书、私钥和 ACME 账号状态写入数据目录;后台续期仍会再次使用验证入口。

自动 HTTPS 的生效条件

Caddy 的 Automatic HTTPS 文档(在新标签页打开)要求以下条件同时成立:

  • 配置中有完整域名,而不是只有 :80:443http:// 地址;
  • A、AAAA 或 CNAME 最终指向当前入口;
  • Caddy 能监听验证所需端口,或者流量能转发到对应端口;
  • Caddy 数据目录可写,并且在服务重启和容器重建后仍然存在。

最小 Caddyfile 包含域名和上游:

example.com {
    reverse_proxy app:8080
}

Caddy 识别到公开域名后,会管理证书并添加 HTTP 到 HTTPS 的跳转。配置写成 http://example.com 会关闭该站点的自动 HTTPS。

ACME 验证方式取决于入口条件

证书颁发机构必须确认申请者控制当前域名。Let’s Encrypt 的验证方式说明(在新标签页打开)区分了三种常用挑战:

验证方式验证入口适用情况常见失败
HTTP-01http://域名/.well-known/acme-challenge/...,端口 80普通公网网站80 未开放、DNS 指错、请求被其他代理接走
TLS-ALPN-01域名的 443 端口和特定 TLS 握手443 可直接到达 Caddy443 被其他入口终止、SNI 路由错误
DNS-01_acme-challenge TXT 记录通配符、内网站点、入口端口不可开放DNS API 权限、TXT 传播、权威 DNS 选错

HTTP-01 和 TLS-ALPN-01 可以由 Caddy 自动选择。通配符证书只能通过 DNS-01 签发;这要求安装对应 DNS provider 模块,并把最小权限的 API 凭据交给 Caddy。

CA 读取的是权威 DNS 返回的地址,而不是本机缓存。切换记录后直接查询权威 DNS:

dig +short example.com NS
dig @权威DNS地址 example.com A
dig @权威DNS地址 example.com AAAA

AAAA 指向旧服务器也会导致验证失败。暂时没有可用 IPv6 入口时,应删除错误的 AAAA,而不是等待客户端自动改走 IPv4。

容器必须持久化 Caddy 数据目录

Caddy 把证书、私钥、ACME 账号和续期状态保存在数据目录。容器中常用 /data,配置目录通常是 /config

services:
  caddy:
    image: caddy:2
    ports:
      - "80:80"
      - "443:443"
      - "443:443/udp"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
      - caddy_data:/data
      - caddy_config:/config

volumes:
  caddy_data:
  caddy_config:

证书卷不能随发布目录一起清空,也不要在普通部署中运行 docker compose down -v。多个 Caddy 实例共享同一证书存储时,还要使用支持锁和一致性的存储后端;简单复制目录不能替代协调机制。

从公网确认签发结果

先检查配置,再从服务外部验证 DNS、HTTP 跳转和 TLS:

caddy validate --config /etc/caddy/Caddyfile

curl -I http://example.com/
curl -I https://example.com/

openssl s_client -connect example.com:443 \
  -servername example.com </dev/null 2>/dev/null \
  | openssl x509 -noout -subject -issuer -dates -ext subjectAltName

检查结果应为:

  • HTTP 返回到同一域名 HTTPS 地址的永久跳转;
  • TLS 握手成功,证书 SAN 包含当前域名;
  • 证书未过期,颁发者和证书链符合预期;
  • HTTPS 请求返回应用的预期状态,而不是入口的默认站点。

openssl s_client -status 可以查看服务端是否返回 OCSP stapling 信息,但证书续期监测仍应以到期时间和证书管理日志为主。不同 CA、证书类型和服务端配置下,OCSP 行为并不完全相同。

续期不应依赖人工记日期

Caddy 会在后台管理证书并按需续期。证书有效期仍需独立监测,因为下一次 ACME 验证、存储写入或证书替换都可能失败。

domain=example.com
expiry="$(
  openssl s_client -connect "${domain}:443" -servername "${domain}" </dev/null 2>/dev/null \
    | openssl x509 -noout -enddate \
    | cut -d= -f2-
)"

openssl s_client -connect "${domain}:443" -servername "${domain}" </dev/null 2>/dev/null \
  | openssl x509 -checkend $((30 * 24 * 60 * 60)) -noout

printf 'certificate_expires=%s\n' "${expiry}"

证书监控应覆盖剩余有效期、Caddy 证书管理错误日志、80/443 外部可达性,以及数据卷空间和写权限。告警阈值要早于证书到期,给 DNS、CA 或权限故障留下处理时间。

失败时按 ACME 验证顺序排查

日志或现象优先检查恢复方式
NXDOMAIN、找不到授权记录域名拼写、NS、A/AAAA/CNAME在权威 DNS 修正记录,等待权威应答稳定
HTTP challenge 超时80 端口、安全组、防火墙、端口转发让公网 80 到达当前 Caddy,或改用 DNS-01
TLS-ALPN challenge 失败443 的终止入口、SNI 和转发关系让验证握手到达负责证书的实例
DNS challenge 找不到 TXTDNS API 权限、zone 选择、传播时间查询权威 DNS,修正凭据作用域和记录位置
重建后重复申请/data 未持久化或权限改变恢复原卷并修正属主,不要继续创建新卷
CA rate limit高频重试或重复域名集合申请停止无效重试,修复根因;测试改用 staging

Let’s Encrypt 的限额文档(在新标签页打开)会随政策更新。不要把某个限额数字写死进部署脚本;自动化应识别 CA 返回的具体错误和可重试时间。

首次配置或调整 ACME 验证方式时,先使用 staging;确认 DNS、端口和存储都正确后,再切回生产 CA。修改多项配置时合并为一次 reload,避免干扰正在进行的 ACME 事务。

证书链稳定后,再配置对象存储和 CDN 的品牌域名;跨云接入过程见阿里云 OSS 绑定自定义 CDN 域名

常见问题

Caddy 会自动申请和续期 HTTPS 证书吗?

会。域名、DNS、验证入口和持久化数据目录满足条件时,Caddy 会申请证书、启用 HTTPS、添加 HTTP 跳转并在后台续期。

申请证书一定要开放 80 端口吗?

不一定。HTTP-01 使用 80,TLS-ALPN-01 使用 443,DNS-01 通过 TXT 记录验证。通配符证书必须使用 DNS-01。

Caddy 容器重建后为什么重新申请证书?

通常是 /data 没有持久化,或者新容器无法读取原卷。恢复原数据卷和正确权限后再启动,避免消耗新的签发请求。

怎样确认自动续期真的可用?

检查证书剩余时间、证书管理日志、数据目录持久化和公网 TLS 握手。浏览器当前能访问,只证明现有证书仍可用。

证书申请失败后可以反复重启 Caddy 吗?

不建议。先修正 DNS、端口、权限或验证方式。测试环境使用 ACME staging,避免无效重试触发生产 CA 限额。