Skip to content

使用 Cloudflare DNS-01 申请 Let’s Encrypt 证书

校对日期:2026-09-04。本文适用于 Ubuntu 服务器,域名 DNS 由 Cloudflare 托管,并由 Nginx 加载证书的场景。

DNS-01 验证不需要对外开放 80 端口,也是申请通配符证书的方式。Certbot 会记住首次申请时使用的 Cloudflare 插件和参数,以后续签时会自动复用。


1. 安装 Certbot 和 Cloudflare 插件

Certbot 官方对大多数 Ubuntu 用户推荐使用 Snap,但也可以使用 Ubuntu/Debian 软件源中的 apt 版本。两种方案二选一,不要混装 Certbot 主程序和不同来源的 DNS 插件。如果服务器已经安装了可正常续签的 Certbot,可直接跳过本节。

方案 A:Snap(官方推荐)

bash
sudo apt update
sudo apt install -y snapd
sudo snap install --classic certbot

如果 /usr/local/bin/certbot 尚不存在,创建官方建议的命令链接:

bash
sudo ln -s /snap/bin/certbot /usr/local/bin/certbot

允许 DNS 插件与 Certbot 拥有相同的 classic 权限,然后安装 Cloudflare 插件:

bash
sudo snap set certbot trust-plugin-with-root=ok
sudo snap install certbot-dns-cloudflare

方案 B:apt(备选)

如果希望 Certbot 由系统软件源统一管理,可安装 Ubuntu/Debian 提供的 Certbot 和 Cloudflare 插件:

bash
sudo apt update
sudo apt install -y certbot python3-certbot-dns-cloudflare

apt 版本由发行版维护,版本可能落后于 Certbot 上游最新稳定版。DNS-01、自动续签和 deploy hook 均可使用;如果某个命令行参数无法识别,应以已安装版本的 certbot --help 为准,或改用 Snap 版本。定时续签可能由 certbot.timer 或发行版提供的 cron 执行,仍应按第 5 节的命令确认。

验证安装

bash
certbot --version
sudo certbot plugins

plugins 的输出中应当包含 dns-cloudflare


2. 创建最小权限的 Cloudflare API Token

  1. 打开 Cloudflare API Tokens
  2. 选择 Create Token,然后使用 Edit zone DNS 模板。
  3. 权限保留 ZoneDNSEdit/Write
  4. 在 Zone Resources 中选择 Specific Zone,仅授权需要申请证书的域名。
  5. 创建并立即复制 Token;Cloudflare 不会再次显示完整密钥。

不要使用权限过大的 Global API Key。Certbot Cloudflare 插件官方文档要求的最小权限是目标 Zone 的 Zone:DNS:Edit


3. 保存 Cloudflare 凭据

将凭据放到仅 root 可读的目录:

bash
sudo install -d -m 700 /etc/letsencrypt/secrets
sudo nano /etc/letsencrypt/secrets/cloudflare.ini

写入:

ini
# Cloudflare API token used by Certbot
dns_cloudflare_api_token = YOUR_CLOUDFLARE_API_TOKEN

限制文件权限:

bash
sudo chmod 600 /etc/letsencrypt/secrets/cloudflare.ini

Certbot 会在续签配置中记录这个文件的路径,不会复制 Token 内容。不要移动或删除该文件,否则自动续签会失败。


4. 申请证书

以同时覆盖根域名和一级子域名的证书为例,将 example.com 替换为自己的域名:

bash
sudo certbot certonly \
  --dns-cloudflare \
  --dns-cloudflare-credentials /etc/letsencrypt/secrets/cloudflare.ini \
  -d example.com \
  -d '*.example.com'

通配符域名必须加引号,避免被 Shell 展开。*.example.com 不覆盖 example.com,因此如果两者都需要,必须同时传入两个 -d

Cloudflare 插件默认等待 10 秒后再验证 DNS。只有在实际遇到 DNS 传播不及时时,才需要增加等待时间,例如:

bash
--dns-cloudflare-propagation-seconds 30

查看 Certbot 管理的证书和实际路径:

bash
sudo certbot certificates

常用文件位于:

  • 完整证书链:/etc/letsencrypt/live/<certificate-name>/fullchain.pem
  • 私钥:/etc/letsencrypt/live/<certificate-name>/privkey.pem

live 下的文件是会随续签自动更新的链接,使用证书的服务应直接引用这些路径,不要手工复制证书。


5. 配置续签成功后的 Nginx Hook

确认自动续签

大多数 Certbot 安装方式已预置定时任务,会周期性执行 certbot renew。Snap 版本也自带续签计时器,因此不需要再添加 crontab。

查看当前的安装来源:

bash
command -v certbot
readlink -f "$(command -v certbot)"

查找已有的 systemd 计时器:

bash
systemctl list-timers --all | grep -i certbot

Snap 安装通常可以继续查看:

bash
systemctl status snap.certbot.renew.timer
snap services certbot

apt/deb 安装通常使用:

bash
systemctl status certbot.timer

个别发行版可能使用 cron,可以检查:

bash
sudo grep -R "certbot renew" /etc/crontab /etc/cron.* 2>/dev/null

创建永久 deploy hook

下面的参数只对当次手工执行生效,不会因此成为以后自动续签的永久配置:

bash
sudo certbot renew --deploy-hook "systemctl reload nginx"

永久做法是把可执行脚本放入 Certbot 的 renewal-hooks/deploy 目录。deploy hook 只会在证书成功签发或续签后执行;没有证书需要续签时,不会 reload Nginx。

先确认命令的绝对路径:

bash
command -v nginx
command -v systemctl

创建 hook:

bash
sudo install -d -m 755 /etc/letsencrypt/renewal-hooks/deploy
sudo nano /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh

写入以下内容。如果上一步的命令路径不同,应按实际输出修改:

sh
#!/bin/sh
/usr/sbin/nginx -t -q && /usr/bin/systemctl reload nginx

赋予执行权限:

bash
sudo chmod 755 /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh

nginx -t 先检查配置;只有检查成功,&& 后的 reload 才会执行。-q 会隐藏配置检查成功时的非错误信息,但真正的配置错误仍会输出并阻止 reload。

目录中的 deploy hook 对该 Certbot 配置管理的所有证书生效。如果同一台机器还管理不供 Nginx 使用的证书,应根据 RENEWED_DOMAINSRENEWED_LINEAGE 在脚本内做条件判断。

测试续签和 hook

普通 --dry-run 只测试续签,默认不执行 deploy hook:

bash
sudo certbot renew --dry-run

要同时测试新增的 Nginx hook,使用:

bash
sudo certbot renew --dry-run --run-deploy-hooks

--run-deploy-hooks 只会在 dry run 续签成功后调用 hook,并使用当前正在生效的证书,不会把 Let’s Encrypt staging 的测试证书交给 Nginx。

为什么会看到 ran with error output

如果 hook 中使用的是未加 -qnginx -t,dry run 可能出现:

text
Hook 'deploy-hook' ran with error output:
 nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
 nginx: configuration file /etc/nginx/nginx.conf test is successful

这通常不是失败。Nginx 会把 -t 的诊断信息,包括 syntax is oktest is successful,写入标准错误流(stderr);Certbot 因此将这段内容标记为 error output。它表示“hook 向 stderr 写了内容”,不等于“hook 返回失败”。

判断时应注意:

  • test is successful 表示 Nginx 配置检查成功。
  • 真正的 hook 失败通常还会出现 reported error codetest failed 或 Nginx [emerg] 信息。
  • 本文脚本加了 nginx -t -q,可避免成功信息造成这种误解。

首次 dry run 显示 Account registered. 也属于正常现象:Certbot 可能正在 Let’s Encrypt staging 环境注册测试账户。Waiting 10 seconds for DNS changes to propagate 则是 Cloudflare 插件的默认 DNS 等待时间。

最终流程是:

text
Certbot 定时检查 → 证书进入续签窗口 → 续签成功
                    → nginx -t -q 成功 → reload Nginx

6. 安全删除证书(可选)

先查看证书的准确名称:

bash
sudo certbot certificates

删除前,必须确认 Nginx、Apache、Postfix 等服务已不再引用它。可先搜索相关路径,将 example.com 替换为 certbot certificates 显示的 Certificate Name:

bash
sudo grep -R "live/example.com" /etc/nginx /etc/apache2 /etc/httpd /etc/postfix 2>/dev/null

先移除或替换这些引用并验证相关服务,然后再让 Certbot 删除证书:

bash
sudo certbot delete --cert-name example.com

不要手工删除 /etc/letsencrypt/livearchiverenewal 内的文件。certbot delete 也不等于吊销证书;只有确实需要提前使证书失效时,才使用 certbot revoke

如果这台服务器已不再有任何证书需要 Cloudflare Token,再到 Cloudflare 控制台撤销 Token,并删除本地凭据:

bash
sudo rm /etc/letsencrypt/secrets/cloudflare.ini

只有在没有任何 Certbot 证书供 Nginx 使用时,才删除全局 hook:

bash
sudo rm /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh

如果不再使用该 ACME 账户,可选择注销:

bash
sudo certbot unregister

参考资料