用 Caddy 自动申请主域名、子域名和自定义域名的 HTTPS 证书

SaaS 支持客户子域名和自定义域名是很常见的需求。Caddy 是一个支持自动申请 HTTPS 证书的 Web server。最近用 Caddy 实现了子域名和自定义域名的自动 HTTPS 配置,记录如下以供参考。

主域名证书

Caddyfile 内容如下:

domain.com {
  reverse_proxy localhost:8080
}

DNS 只需要将域名 A 记录指向 IP,Caddy 即可自动申请 Let's Encrypt 的证书。

⚠️ Caddy 的 data 目录存放着申请到的证书,必须将它配置在持久化的储存卷。

子域名证书

为了支持无限多的子域名,子域名需要使用通配符证书。对于通配符证书 Caddy 需要有操作 DNS 记录的权限以完成证书颁发的 DNS 验证。

要操作 DNS,Caddy 需要安装扩展。以 Cloudflare DNS 为例,首先安装 Caddy 的 cloudflare 扩展:

FROM caddy:2.6-builder AS builder

RUN xcaddy build \
    --with github.com/caddy-dns/cloudflare

FROM caddy:2.6

COPY --from=builder /usr/bin/caddy /usr/bin/caddy

然后编辑 Caddyfile 配置:

*.sitedomain.com {
  reverse_proxy localhost:8080

  tls {
    dns cloudflare {env.CF_API_TOKEN}
  }
}

其中 sitedomain.com 域名 DNS 需要托管在 Cloudflare,并且获取 API Token 放到 Caddyfile 的配置中。

你可以在这里查看所有支持的 DNS:https://github.com/caddy-dns

自定义域名

自定义域名由用户设置,不能硬编码到配置文件中。Caddy 通过 On-Demand TLS 这个特性支持自定义域名的 HTTPS 配置。开启 On-Demand TLS 后,每当有一个域名请求到达 Caddy,Caddy 会自动为这个域名申请证书。

但如果对域名不做限制,可能会遇到一些恶意请求消耗证书申请的额度,导致无法申请正常的域名证书。所以 Caddy 提供了一个配置,可以在申请证书之前请求一个 API,获得 HTTP 200 响应才继续申请证书。于是在应用层要实现一个校验域名是否有效的接口,这里假设实现为 localhost:8080/check,Caddy 会发出一个 check?domain=xxx 的 GET 请求,应用根据自己的逻辑判断是否返回 200 状态。

在 Caddyfile 的顶部添加全局配置:

{
  on_demand_tls {
    ask http://localhost:8080/check
  }
}

在 Caddyfile 底部添加捕获所有域名的配置:

https:// {
  reverse_proxy localhost:8080

  tls {
    on_demand
  }
}

这样 Caddy 在遇到一个未签发证书的域名时,会先请求 /check API 校验域名是否有效,通过之后再申请证书。

配置汇总

以上所有配置汇总如下:

{
  on_demand_tls {
    ask http://localhost:8080/check
  }
}

domain.com {
  reverse_proxy localhost:8080
}

*.sitedomain.com {
  reverse_proxy localhost:8080

  tls {
	dns cloudflare {env.CF_API_TOKEN}
  }
}

https:// {
  reverse_proxy localhost:8080

  tls {
    on_demand
  }
}

现在 Caddy 已经支持自动为主域名、子域名和自定义域名申请 HTTPS 证书。

4
3
2