frankenphp不限制证书数量,但let’s encrypt协议禁止通配符与多主域混用;正确做法是同一主域用san证书(如example.com www.example.com),跨主域则分块申请独立证书。

FrankenPHP不支持多主域共用一张通配符证书
直接说结论:FrankenPHP 本身不限制证书数量,但 Let’s Encrypt 的 ACME 协议禁止将多个独立主域名(如 example.com 和 another.net)与通配符(如 *.example.com)混在同一张证书中。你尝试在 Caddyfile 里写 example.com、www.example.com、another.net 一起申请,Caddy 会静默失败或报 error: too many authorizations。
Caddyfile 中正确配置多域名 SAN 证书
Caddy 默认走的就是 SAN(Subject Alternative Name)证书路线,只要所有域名都属于同一主域或可明确列出,它就能自动申请并续期一张覆盖全部域名的证书。
- 确保所有域名 DNS 已解析到服务器 IP,且 80/443 端口可被公网访问
- 在
Caddyfile中把域名全部列在同一个站点块首行,用空格分隔,不要加*:example.com www.example.com api.example.com shop.example.com { php_server } - 不要为每个子域单独写一个
{...}块——那样会触发多次独立申请,容易触发 Let’s Encrypt 频率限制 - 证书生成后默认存于
/data/certificates/acme-v02.api.letsencrypt.org/sites/下,按域名哈希组织,Caddy 自动管理,无需手动干预
想用通配符?必须 DNS 验证 + 单主域
如果你确实需要 *.example.com(比如子域动态创建、无法预知全量列表),那就不能依赖 Caddy 的 HTTP 验证模式,必须切到 DNS 验证,并且只能覆盖单个主域。
- 修改
Caddyfile,显式指定 DNS 提供商插件(以 Cloudflare 为例):{ acme_dns cloudflare { api_token {env.CLOUDFLARE_API_TOKEN} } } *.example.com { php_server } - 环境变量
CLOUDFLARE_API_TOKEN必须在启动 FrankenPHP 容器时注入(Docker 或 systemd) -
*.example.com不能和another.net出现在同一行,否则 Caddy 启动失败,日志里会出现invalid domain name - DNS 验证耗时略长(通常 30–90 秒),首次启动需耐心等待,失败时检查 API Token 权限和域名所属 Zone 是否匹配
多个域名用不同证书?Caddy 原生支持,但得分开写块
FrankenPHP + Caddy 允许为不同域名绑定不同证书,前提是它们不共享同一 server_name 块。这适合跨主域场景(如 example.com 和 another.net 各自用独立证书)。
- 每个主域单独一个站点块,且块内只写该域及其子域:
example.com www.example.com { php_server } another.net www.another.net { php_server } - Caddy 会分别为两组域名申请两张独立的 SAN 证书,互不影响
- 注意:不能靠
tls /path/to/cert.pem /path/to/key.pem手动指定路径来“复用”同一张证书文件——Caddy 会校验证书 Subject 和 SAN 字段是否匹配请求的Host,不匹配就 fallback 到自签名证书,浏览器直接报错 - 这种写法对性能无影响,Caddy 内部基于 SNI 正常分发 TLS 握手,HTTP/3 也照常启用
真正容易被忽略的是 DNS 解析状态和 ACME 挑战端口可达性——哪怕 Caddyfile 写对了,只要某个域名的 _acme-challenge TXT 记录没生效,或 80 端口被防火墙拦截,证书就卡在 pending 状态,且不会明确告诉你卡在哪一步。建议先用 dig 和 curl -v http://example.com/.well-known/acme-challenge/test 手动验证基础连通性。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











