使用 Certbot 申请 Let’s Encrypt 通配符证书:从签发到自动续期

讲清 Let’s Encrypt 通配符证书为何必须走 DNS-01,以及如何用 Certbot 完成签发、校验、Nginx/Apache 配置和自动续期。

阅读时长: 11 分钟
共 5253字
作者: eimoon.com

使用 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 发行版。不同发行版的软件包名称会有差异,但核心步骤一致:

  1. 先确认通配符 DNS 解析符合预期。
  2. 安装 Certbot,以及与你的 DNS 服务商匹配的插件;如果没有 API,也可以手工完成 DNS 验证。
  3. 安全保存 DNS API 凭据。
  4. 用正确的 certbot certonly 参数申请证书,包括 apex 域名、多个根域名和多级子域名的场景。
  5. 校验证书输出的 PEM 文件。
  6. 配置 Nginx 或 Apache 使用新证书。
  7. 配置无人值守续期,并在续期成功后自动重载 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.combar.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 调高。

可以用 dighost 检查解析:

dig +short A app.example.com
host app.example.com

在继续申请证书前,至少应当看到有效的 ACNAME 响应。

Certbot 插件该怎么安装

有三种路径:

  1. 完全手工 DNS 验证
  2. 通过系统包管理器安装 DNS 插件
  3. 通过 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,同时也会有 standalonewebroot 等其他插件。

使用 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-challenge TXT 后等待 60 秒,再去做验证

默认等待时间是 10 秒,但很多 DNS 提供商传播没这么快。实际使用中,3060 秒更稳妥;如果验证频繁失败,可以提高到 120 甚至更高。

调试阶段为什么建议用 --staging

调试 DNS、脚本或续期钩子时,建议加上:

--staging

staging 证书不被浏览器信任,但不会消耗正式环境的签发配额,也能避免反复试错触发限流。等整条链路跑通,再去掉 --staging

证书文件会生成到哪里

成功后,Certbot 会把 PEM 文件写到:

/etc/letsencrypt/live/example.com/

目录名由你传入的第一个 -d 决定。常见文件有:

  • fullchain.pem
  • privkey.pem
  • chain.pem
  • cert.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.com
  • v1.api.example.com
  • v2.api.example.com

但不覆盖:

  • example.com
  • *.example.com

如何验证证书内容确实正确

证书签发完成后,至少检查两件事:

  1. SAN 是否包含了需要的域名
  2. 有效期是否正常

查看 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.com
  • DNS:*.example.com

查看有效期:

sudo openssl x509 -in /etc/letsencrypt/live/example.com/cert.pem \
  -noout -dates

输出会包含:

  • notBefore
  • notAfter

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.com
  • ssl_certificate 必须指向 fullchain.pem
  • TLS 协议和 cipher 配置建议直接使用 Mozilla SSL Configuration Generator 生成,选择 NginxIntermediate 配置

其中:

  • 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。真正决定后续是否稳定的,不是签发那一条命令,而是三件事:

  1. 是否正确理解了通配符覆盖范围
  2. 是否用 DNS 插件而不是手工模式
  3. 是否把续期和服务重载完整自动化

生产环境里,推荐做法也很明确:

  • certbot certonly
  • 同时申请 example.com*.example.com
  • 使用 DNS 插件完成自动验证
  • certbot renew --dry-run 验证续期链路
  • 配置 deploy hook 自动 reload Nginx 或 Apache

这样配置之后,通配符证书的运维成本会明显下降,而且不会在 90 天后突然变成一次线上故障。

关于

关注我获取更多资讯

月球基地博客公众号二维码,扫码关注获取更多 AI 与编程资讯
📢 公众号
月球基地博客作者个人微信二维码,扫码交流 AI 与编程话题
💬 个人号
使用 Hugo 构建
主题 StackJimmy 设计