使用 Certbot 申请 Let’s Encrypt 通配符证书:从签发到自动续期
给 *.example.com 这类通配符域名申请 Let’s Encrypt 证书,必须使用 ACME 的 DNS-01 验证,不能使用 HTTP-01。验证方式也不是在某台 Web 服务器上放一个文件,而是在 DNS 里为 _acme-challenge 写入 TXT 记录。
这个限制很合理。*.example.com 代表的是整个域名空间下一层子域名,不是某一台已经上线的主机。HTTP-01 只能证明某个具体主机名可控,DNS-01 才能证明对整个 DNS 区域有控制权。
这套流程适用于常见 Linux 发行版。不同发行版的软件包名称会有差异,但核心步骤一致:
- 先确认通配符 DNS 解析符合预期。
- 安装 Certbot,以及与你的 DNS 服务商匹配的插件;如果没有 API,也可以手工完成 DNS 验证。
- 安全保存 DNS API 凭据。
- 用正确的
certbot certonly参数申请证书,包括 apex 域名、多个根域名和多级子域名的场景。 - 校验证书输出的 PEM 文件。
- 配置 Nginx 或 Apache 使用新证书。
- 配置无人值守续期,并在续期成功后自动重载 Web 服务。
先给出几个容易踩坑的结论:
*.example.com只覆盖api.example.com这种“根域名下一层子域名”,不覆盖example.com本身,也不覆盖dev.api.example.com。- 通配符证书始终使用 DNS 验证。HTTP-01 对普通主机名仍然有效,但对通配符无效。
- 手工
--manual方式可以签发通配符证书,但续期时仍然要重复手工改 TXT 记录,不适合生产环境。 - Let’s Encrypt 证书有效期只有 90 天,生产环境应当把续期自动化。
- 调试阶段建议加
--staging,先避开生产限流,跑通再切换正式环境。
为什么通配符证书必须使用 DNS-01
HTTP-01 的逻辑是:证书颁发机构要求目标主机在 HTTP 路径下返回指定 token,以此证明这个主机名由你控制。
但 *.example.com 的问题在于,它不是一个具体主机,而是一组未来可能出现的主机名。仅凭 app.example.com 上的一次 HTTP 响应,无法证明对 foo.example.com、bar.example.com 乃至尚未解析的名字都有控制权。
DNS-01 则不同。它要求在 _acme-challenge.example.com 下添加 TXT 记录,而这个位置由域名权威 DNS 控制。能修改这里,才算真正控制了这个域名空间。
手工 DNS 验证和 DNS 插件,应该选哪种
结论很直接:
- 偶发、一次性签发:可以用
--manual - 生产环境、需要自动续期:应该用 DNS 插件
| 维度 | 手工 --manual DNS |
DNS 插件 |
|---|---|---|
| API Token | 不需要 | 需要具备 DNS 写权限 |
| 适用场景 | 偶发申请、无 API 环境 | 生产续期 |
| 续期方式 | 每次都要手工改 TXT | certbot renew 自动处理 |
| 交互方式 | Certbot 会暂停等待人工输入 | 可无人值守运行 |
如果没有 DNS API 权限,手工模式仍然可用;但一旦进入长期运维阶段,不自动化基本等于埋雷。
申请前需要准备什么
需要满足这几个前提:
- 一个由你控制 DNS 记录的域名,用于签发和续期
- 一台具有
sudo权限的 Linux 主机 - 已安装较新的 Certbot 版本,来源可以是发行版包管理器或 Snap
在 Ubuntu 上,Snap 已经成为更常见的安装方式。不同安装来源会影响 DNS 插件的安装方法,所以后面的命令要和你的 Certbot 安装方式对应起来。
如果后面需要把证书挂到 Nginx 或 Apache,建议同时确认 Web 服务器已经正常运行,避免签完证书之后再排查服务配置问题。
如何正确设置通配符 DNS
如果希望 *.example.com 都落到某台服务器或负载均衡器上,典型记录如下:
*.example.com. 3600 IN A 203.0.113.1
这里的 * 只匹配最左侧一层标签:
*.example.com匹配app.example.com- 不匹配
example.com - 不匹配
dev.app.example.com
第一次配置通配符解析时,建议把 TTL 临时调低,比如从 3600 降到 300。这样如果记录写错,修正传播会更快。确认签发成功、解析稳定后,再把 TTL 调高。
可以用 dig 或 host 检查解析:
dig +short A app.example.com
host app.example.com
在继续申请证书前,至少应当看到有效的 A 或 CNAME 响应。
Certbot 插件该怎么安装
有三种路径:
- 完全手工 DNS 验证
- 通过系统包管理器安装 DNS 插件
- 通过 Snap 安装 DNS 插件
没有 DNS API 时,如何手工验证
没有 API 访问能力时,可以直接使用:
sudo certbot certonly --manual --preferred-challenges dns \
-d '*.example.com' -d 'example.com'
执行后,Certbot 会打印出需要写入 DNS 的 TXT 值。把记录加到 DNS 服务商后台,等待传播后,再回到终端继续。
这个方式能工作,但它的限制也很明确:每次续期都会停下来等人工处理 TXT 记录。因此 certbot renew 放进 cron 或 systemd timer 后会超时失败。
使用系统包管理器安装 DNS 插件
如果 Certbot 是通过系统包安装的,DNS 插件一般也走同一套包管理器。
以 DigitalOcean 的 DNS 插件为例,Ubuntu / Debian 上的包名是:
sudo apt install python3-certbot-dns-digitalocean
Fedora 或 RHEL 8+:
sudo dnf install python3-certbot-dns-digitalocean
安装后检查插件是否已经注册:
certbot plugins
正常情况下会看到 dns-digitalocean,同时也会有 standalone、webroot 等其他插件。
使用 Snap 安装 DNS 插件
Ubuntu 22.04 及更新版本里,Certbot 常见安装方式是 Snap。此时系统包版插件可能缺失,或者版本滞后。
先安装 Certbot:
sudo snap install --classic certbot
允许 Certbot 使用需要提升权限的插件:
sudo snap set certbot trust-plugin-with-root=ok
安装 DigitalOcean DNS 插件:
sudo snap install certbot-dns-digitalocean
把插件连接到 Certbot:
sudo snap connect certbot:plugin certbot-dns-digitalocean
最后确认插件可见:
certbot plugins
如果输出中出现 dns-digitalocean,就可以继续做凭据配置。
DNS 插件凭据该怎么保存才安全
生产环境里,建议把凭据文件放在 /etc/letsencrypt/ 下。例如:
sudo nano /etc/letsencrypt/certbot-creds.ini
DigitalOcean 插件需要一个 token:
dns_digitalocean_token = paste_your_write_scoped_token_here
这个 token 应当只有 DNS write 权限,而不是整个账号的完全权限。权限越小,泄露后的影响范围越小。
保存后必须收紧文件权限:
sudo chmod 600 /etc/letsencrypt/certbot-creds.ini
如果权限过宽,Certbot 会给出安全警告。
申请通配符证书时,命令应该怎么写
通配符证书统一使用 certbot certonly。--nginx 和 --apache 安装器模式不会替你处理通配符虚拟主机,所以这里没有捷径,直接用 certonly。
自动化签发示例
同时覆盖 apex 和通配符的典型命令如下:
sudo certbot certonly \
--dns-digitalocean \
--dns-digitalocean-credentials /etc/letsencrypt/certbot-creds.ini \
--dns-digitalocean-propagation-seconds 60 \
-d 'example.com' \
-d '*.example.com'
这里有几个关键点:
-d 'example.com'覆盖裸域-d '*.example.com'覆盖一层子域名--dns-digitalocean-propagation-seconds 60指定 Certbot 在创建_acme-challengeTXT 后等待 60 秒,再去做验证
默认等待时间是 10 秒,但很多 DNS 提供商传播没这么快。实际使用中,30 到 60 秒更稳妥;如果验证频繁失败,可以提高到 120 甚至更高。
调试阶段为什么建议用 --staging
调试 DNS、脚本或续期钩子时,建议加上:
--staging
staging 证书不被浏览器信任,但不会消耗正式环境的签发配额,也能避免反复试错触发限流。等整条链路跑通,再去掉 --staging。
证书文件会生成到哪里
成功后,Certbot 会把 PEM 文件写到:
/etc/letsencrypt/live/example.com/
目录名由你传入的第一个 -d 决定。常见文件有:
fullchain.pemprivkey.pemchain.pemcert.pem
一个证书能覆盖哪些域名
一个 certbot certonly 命令可以通过多个 -d 加入多个 SAN。常见场景有三类。
apex 和通配符一起申请
最常见,也最推荐:
-d 'example.com' -d '*.example.com'
只申请 *.example.com 不会覆盖 example.com。这是最常见的错误之一。
多个根域名合并到一个证书
如果同时管理多个域名,也可以合并在一个证书里:
sudo certbot certonly \
--dns-digitalocean \
--dns-digitalocean-credentials /etc/letsencrypt/certbot-creds.ini \
--dns-digitalocean-propagation-seconds 60 \
-d 'example.com' -d '*.example.com' \
-d 'example.net' -d '*.example.net'
这样做的好处是续期任务更少,但也有代价:某一个 SAN 出问题,可能影响整个证书续期。域名数量增加时,要考虑管理边界和故障影响面。
多级子域名需要单独申请
ACME 规范只允许单层通配符,所以:
*.example.com不匹配v2.api.example.com- 如果要覆盖
api.example.com这一整棵子树,需要单独申请:
sudo certbot certonly \
--dns-digitalocean \
--dns-digitalocean-credentials /etc/letsencrypt/certbot-creds.ini \
--dns-digitalocean-propagation-seconds 60 \
-d 'api.example.com' \
-d '*.api.example.com'
这个证书覆盖:
api.example.comv1.api.example.comv2.api.example.com
但不覆盖:
example.com*.example.com
如何验证证书内容确实正确
证书签发完成后,至少检查两件事:
- SAN 是否包含了需要的域名
- 有效期是否正常
查看 SAN:
sudo openssl x509 -in /etc/letsencrypt/live/example.com/cert.pem \
-noout -text | grep -A1 "Subject Alternative Name"
如果是通过 -d 'example.com' -d '*.example.com' 申请的,输出里应当同时看到:
DNS:example.comDNS:*.example.com
查看有效期:
sudo openssl x509 -in /etc/letsencrypt/live/example.com/cert.pem \
-noout -dates
输出会包含:
notBeforenotAfter
Let’s Encrypt 证书通常大约 90 天有效。如果发现 notAfter 很快就到期,往往不是新证书没生效,而是 Web 服务还在提供旧证书。
四个 PEM 文件分别是干什么的
| 文件 | 内容 | 用途 |
|---|---|---|
fullchain.pem |
站点证书 + 中间 CA 证书 | Web 服务器对外提供的证书文件 |
privkey.pem |
私钥 | 服务器私钥 |
cert.pem |
仅站点证书 | 适合查看,不适合作为生产 ssl_certificate |
chain.pem |
仅中间 CA 证书 | 某些特定 TLS 配置需要 |
结论很明确:Nginx 和 Apache 一般都应当指向 fullchain.pem,不要用 cert.pem 替代。
Nginx 应该如何使用通配符证书
certonly 只负责拿证书,不会改 Web 服务器配置。Nginx 需要手工指定证书路径。
一个可用的示例:
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name example.com *.example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
# Paste TLS settings from https://ssl-config.mozilla.org (Nginx, Intermediate)
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
root /var/www/html;
index index.html;
}
# Redirect HTTP to HTTPS
server {
listen 80;
listen [::]:80;
server_name example.com *.example.com;
return 301 https://$host$request_uri;
}
几点说明:
server_name同时写example.com和*.example.comssl_certificate必须指向fullchain.pem- TLS 协议和 cipher 配置建议直接使用 Mozilla SSL Configuration Generator 生成,选择 Nginx 和 Intermediate 配置
其中:
ssl_session_cache shared:SSL:10m表示共享 10MB TLS 会话缓存,大约可容纳 4 万个会话ssl_session_timeout 1d表示会话缓存有效期 1 天
修改完配置后测试并重载:
sudo nginx -t && sudo systemctl reload nginx
Apache 应该如何使用通配符证书
Apache 的等价配置如下:
<VirtualHost *:443>
ServerName example.com
ServerAlias *.example.com
SSLEngine on
SSLCertificateFile /etc/letsencrypt/live/example.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/example.com/privkey.pem
# Paste TLS settings from https://ssl-config.mozilla.org (Apache, Intermediate)
DocumentRoot /var/www/html
</VirtualHost>
# Redirect HTTP to HTTPS
<VirtualHost *:80>
ServerName example.com
ServerAlias *.example.com
RewriteEngine On
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
</VirtualHost>
这里的关键是:
ServerAlias *.example.com决定 Apache 接收通配符子域名SSLCertificateFile指向fullchain.pem- 在 Apache 2.4.8 及以后,不再需要单独使用
SSLCertificateChainFile
如果重定向使用了 rewrite,需要启用相关模块:
sudo a2enmod rewrite ssl headers
测试并重载:
sudo apachectl configtest && sudo systemctl reload apache2
在 RHEL 系发行版上,服务名通常是 httpd,命令要相应调整。
为什么手工模式不能自动续期
如果证书最初是通过 --manual 签发的,Certbot 会在续期配置里记录:
[renewalparams]
authenticator = manual
这意味着每次续期都要停下来,等待人工去更新 TXT 记录。放进 cron 或 systemd timer 后,没有人响应,续期自然就失败。
如果最初是通过 DNS 插件签发的,配置会变成:
[renewalparams]
authenticator = dns-digitalocean
dns_digitalocean_credentials = /etc/letsencrypt/certbot-creds.ini
这种配置下,certbot renew 才能完整自动执行 DNS-01。
如何确认续期配置是正确的
先看续期文件:
sudo cat /etc/letsencrypt/renewal/example.com.conf
重点检查 [renewalparams] 段里是否为:
authenticator = dns-digitalocean
如果还是 manual,说明当前证书仍然沿用旧的手工方式。这时不要去硬改配置文件,最稳妥的方法是按插件方式重新签发一次,Certbot 会更新这份续期配置。
如何确认自动续期任务已经存在
在 Ubuntu / Debian 上,Certbot 通常会安装 systemd timer。具体单元名取决于安装方式:
systemctl status certbot.timer snap.certbot.renew.timer
也可以单独检查:
systemctl status certbot.timer
如果系统里没有 timer,比如是通过 pip 安装的,可以补一个 cron 任务:
0 */12 * * * root certbot renew --quiet
如果放在 /etc/cron.d/certbot,要保留 root 这一列;如果是通过 sudo crontab -e 添加到 root 的 crontab,则不要写用户名列。
自动续期上线前,该怎么测试
续期逻辑必须先跑一次 dry run:
sudo certbot renew --dry-run
这会使用 Let’s Encrypt 的 staging 环境模拟完整 ACME 交互,包括 DNS-01 验证,但不会使用正式 CA。
如果 dry run 失败,常见原因通常是:
- 凭据文件路径写错
- Token 权限不足
- DNS 传播等待时间太短
- 续期配置仍然是
manual
这些问题要在证书快到期前解决,而不是等线上过期后再排查。
续期成功后,如何自动重载 Nginx 或 Apache
证书文件更新后,Web 服务不一定会立即重新读取。很多服务需要一次 reload 才会加载新证书。因此需要 deploy hook。
在签发时直接写入 deploy hook
例如:
sudo certbot certonly \
--dns-digitalocean \
--dns-digitalocean-credentials /etc/letsencrypt/certbot-creds.ini \
--dns-digitalocean-propagation-seconds 60 \
-d 'example.com' -d '*.example.com' \
--deploy-hook "systemctl reload nginx"
这样 Certbot 会把 hook 保存进 /etc/letsencrypt/renewal/example.com.conf,后续每次自动续期成功都会执行。
对已有证书统一配置 deploy hook
也可以把脚本放进全局 deploy hooks 目录:
sudo nano /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh
脚本内容:
#!/bin/sh
systemctl reload nginx
加执行权限:
sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh
这种方式会对这台机器上所有 Certbot 管理的证书生效,适合多个证书都由同一个 Nginx 或 Apache 服务承载的场景。
需要区分几类 hook:
deploy-hook:只有证书真的完成续期后才执行pre-hook/post-hook:每次尝试续期都会执行,不管证书有没有更新
对于 DNS 插件场景,绝大多数情况下只需要 deploy-hook。
常见故障该怎么排查
_acme-challenge 的 TXT 记录看不到
先查权威 DNS 是否已经返回 TXT:
dig TXT _acme-challenge.example.com +short
如果没有看到预期值,说明还没传播完成。
处理方式:
- 继续等待传播
- 如果经常失败,增加等待时间,例如:
--dns-digitalocean-propagation-seconds 120
证书不包含裸域名 example.com
通常是因为签发时只写了:
-d '*.example.com'
修正方式是重新签发,并加上:
-d example.com -d '*.example.com'
通配符不会自动覆盖 apex 域名。
*.example.com 不匹配 v2.api.example.com
这是通配符语义本身的限制,只匹配一层。
要覆盖这类名字,需要额外申请:
-d 'api.example.com' -d '*.api.example.com'
DNS 插件认证失败
如果续期时报认证或 DNS API 错误,优先检查:
- token 是否已经失效,需要轮换
- token 是否具备 DNS write 权限
- 凭据文件路径是否和
/etc/letsencrypt/renewal/*.conf中保存的一致
触发 Let’s Encrypt 频率限制
如果出现 “Too many certificates” 之类提示,先切到 staging:
--staging
把根因修复后,再去掉 --staging 重新申请正式证书。
续期配置仍然是 manual
看 /etc/letsencrypt/renewal/example.com.conf,如果还是:
authenticator = manual
说明当前续期流程仍需要人工参与。重新用 DNS 插件方式签发一次即可。
Certbot 已续期,但站点仍提供旧证书
这种情况通常不是 Certbot 没更新,而是 Web 服务没有 reload。
先检查是否存在 deploy hook:
ls /etc/letsencrypt/renewal-hooks/deploy/
如果没有,手工重载一次:
sudo systemctl reload nginx
后续再补 deploy hook,避免下次重复出问题。
几个边界问题
apex 和通配符要不要分成两个证书
一般不需要。大多数情况下,把 example.com 和 *.example.com 放进同一个证书最简单,续期也只需要一条链路。
*.api.example.com 和 *.example.com 能不能放在同一个证书里
可以。直接追加一个 SAN:
-d '*.api.example.com'
Certbot 会分别为不同的通配符标签创建对应的 DNS-01 验证,也就是:
_acme-challenge.example.com_acme-challenge.api.example.com
这在技术上完全可行,但从运维隔离角度看,多个通配符混在一个证书里会扩大故障影响面。是否拆分,取决于你的管理边界。
通配符证书能不能完全替代每主机一个证书
不能一概而论。
通配符的优点是证书数量少、运维成本低;缺点是私钥一旦泄露,影响范围更大。很多团队会混用:
- 边缘层或负载均衡使用通配符证书
- 内部服务或关键服务使用单独证书
这比“全部通配符”或“全部单主机证书”更符合实际。
结论
Let’s Encrypt 通配符证书的核心约束很简单:只能走 DNS-01。真正决定后续是否稳定的,不是签发那一条命令,而是三件事:
- 是否正确理解了通配符覆盖范围
- 是否用 DNS 插件而不是手工模式
- 是否把续期和服务重载完整自动化
生产环境里,推荐做法也很明确:
- 用
certbot certonly - 同时申请
example.com和*.example.com - 使用 DNS 插件完成自动验证
- 用
certbot renew --dry-run验证续期链路 - 配置 deploy hook 自动 reload Nginx 或 Apache
这样配置之后,通配符证书的运维成本会明显下降,而且不会在 90 天后突然变成一次线上故障。
关于
关注我获取更多资讯