关于 Let's Encrypt 证书签发的杂谈
前言
申请证书时,Certbot 却一直超时。有时是验证方访问不到站点,有时是服务器自己连不上证书签发接口,这两类问题处理方向不同。
先看失败发生在哪一段连接,再决定修正的配置、改用 DNS-01,还是换一台签发机。证书拿到之后,还要完成安装和续期,不能只停在“签发成功”。
先判断失败方向
签发机 → ACME API:创建订单、领取挑战、提交验证、下载证书
验证方 → 站点或 DNS:确认域名控制权| 日志表现 | 优先检查 |
|---|---|
| 指向 ACME API 的连接、TLS 或读取超时 | 签发机的 DNS、出站网络、代理和 TLS 环境 |
拉取 /.well-known/acme-challenge/ 超时或内容错误 | HTTP 入口、重定向、反代、端口和文件路径 |
查询 TXT、CAA 出现 SERVFAIL,或 DNSSEC 错误 | 权威 DNS、委派、传播及 DNSSEC 配置 |
During secondary validation | 继续看后面的具体错误;不同验证位置的结果可能不一致 |
| 插件、参数或权限报错 | 本机 Certbot 版本、安装来源、插件和配置 |
HTTPSConnectionPool 只是连接异常的包装信息,要看其中的主机名和详细原因。ACME API 在多个阶段都会被调用,不能据此断言“订单还没创建”。
同样,During secondary validation 不代表自身配置一定正确。一个解析器返回正常、Apache 语法通过,也不足以排除 IPv6、DNSSEC、CAA 或多个入口配置不一致。
检查签发机能否连接 ACME API
在运行 Certbot 的机器上执行:
curl -4 -sS --connect-timeout 10 --max-time 25 \
-o /dev/null \
-w 'http=%{http_code} connect=%{time_connect}s tls=%{time_appconnect}s total=%{time_total}s\n' \
https://acme-v02.api.letsencrypt.org/directory本机配置了 IPv6 时,再将 -4 换成 -6 对照。检查是否经过代理,确认 curl 与 Certbot 的网络环境一致。可以从另一台机器交叉测试,但不要连续创建正式订单来试网络。
TCP 连接成功、后续读取超时,只能缩小排查范围,不能直接证明是运营商限速。还要检查代理、路由、TLS、系统时间和远端状态。
DNS-01 不会绕过 ACME API。 这条出站连接不通,换挑战类型也解决不了。先恢复连接;需要异机签发时,按后文同时安排证书安装和续期。
检查验证入口
HTTP-01 从公网 80 端口访问挑战路径。确认 DNS 的 A、AAAA 都指向可用入口,多台反代或负载均衡节点返回相同挑战内容,路径没有被业务路由、登录或错误重定向接管。
DNS 查询失败则检查权威记录和实际委派,不只查本机缓存。开启代理、拿到合成 IP 的开发机也不适合单独作为 DNS 结论依据。
如果 HTTP 挑战入口难以稳定提供,而 DNS 控制正常,可以使用 DNS-01。它验证 _acme-challenge 下的 TXT 记录,不要求访问网站的 80 端口,也支持通配符证书;但 DNS-01 仍依赖 DNS 查询和 CAA 等签发检查,不能治好所有 DNS 故障。具体要求见 Let’s Encrypt 挑战类型。
准备 DNS-01 配置
| 配置项 | 怎样确定 |
|---|---|
| 申请域名 | 列出证书需要覆盖的所有名称,不把证书名称与域名列表混为一谈 |
| DNS 区域和记录名 | 从服务商实际托管区域或权威委派确定,不直接截取域名最后两段 |
| 插件或钩子 | 根据 DNS 服务商、Certbot 版本和安装方式选择,确认兼容后再安装 |
| DNS 凭据 | 使用独立、最小权限凭据,尽量限定区域或挑战记录,存放在发布目录之外 |
| 挑战记录 | 使用本次验证生成的值,等待权威节点可见,清理时只删除本次创建的值 |
| 签发与安装位置 | 确认谁运行 Certbot、谁提供 HTTPS,以及续期后如何部署 |
DNS 修改权限并不是低风险权限,应尽可能缩小范围;支持时可将挑战子域委派到独立区域。凭据文件限制读取权限,不打印内容或打进发布包。
现成且兼容的插件优先。手动模式配合自动化钩子也可以使用,并非只适用于旧版本;反过来,纯交互式手填 TXT 并不等于已经具备自动续期能力。
钩子应完成什么
认证钩子读取 CERTBOT_DOMAIN、CERTBOT_VALIDATION,在正确区域写入 TXT,并等待传播;清理钩子删除它自己创建的记录。通配符和普通域名可能同时需要同名 TXT,不能清空整组记录。
上线前在不与签发任务冲突的测试域名上验证“写入 → 查询 → 清理”,失败要返回非零退出码。不要固定认为等待 60 秒一定足够,也不要把 .co.uk 等域名直接拆成最后两段。
下面仅演示已经实现并验证钩子后的调用方式。脚本路径、域名和邮箱都需要替换;插件模式则使用对应插件参数,不混抄两套命令。
DOMAIN='example.com'
EMAIL='admin@example.com'
AUTH_HOOK='/受控路径/dns-auth.sh'
CLEANUP_HOOK='/受控路径/dns-cleanup.sh'
certbot certonly --manual --preferred-challenges dns \
-d "$DOMAIN" --email "$EMAIL" --agree-tos \
--manual-auth-hook "$AUTH_HOOK" \
--manual-cleanup-hook "$CLEANUP_HOOK"条款与账号信息先确认,参数以当前安装版本为准。不要为了兼容示例,把系统包管理器维护的 Certbot 与另一套 pip 依赖混装。参考 Certbot 手动模式。
使用另一台机器签发
DNS-01 不要求签发机就是 Web 服务器。原服务器出站故障时,可以在网络正常、受信任的机器签发,再把证书部署到 HTTPS 入口。
签发机 → ACME API
│
└─ DNS 插件或受限远程钩子 → 权威 DNS
签发成功 → 受控传输 → HTTPS 入口安装 → 配置检查与重载DNS 凭据可以留在原来的受控环境,由签发机调用受限远程钩子。远端只允许处理指定区域的挑战操作;域名、TXT 值和操作类型通过结构化输入传递并验证,不直接拼进任意 shell 命令,也不因此授予完整服务器管理权限。
在签发机保留独立的配置、工作和日志目录。那里不仅有证书,还包含 ACME 账号和续期状态;不是签完后就可以删除的临时文件。
证书和私钥通过验证过主机身份的加密连接传输。安装机使用独立版本目录,不手工把外来文件塞进另一套 Certbot 管理的 live/archive/renewal 结构。
安装并验证证书
先备份当前站点配置,保存仍可用的证书回退点。新证书目录限制访问,私钥仅允许必要服务读取。
下面在证书所在目录执行,以公钥比对证书和私钥是否配对。示例需要 Bash;临时目录中只写入公钥:
(
set -euo pipefail
TMP_DIR=$(mktemp -d)
trap 'rm -rf "$TMP_DIR"' EXIT
openssl x509 -in fullchain.pem -noout -pubkey > "$TMP_DIR/cert.pub"
openssl pkey -in privkey.pem -pubout > "$TMP_DIR/key.pub"
cmp "$TMP_DIR/cert.pub" "$TMP_DIR/key.pub"
openssl x509 -in fullchain.pem -noout -dates -ext subjectAltName
)配对正确还不够:检查 SAN 覆盖目标域名、有效时间和证书链。不要只看 subject 或 issuer 是否包含某个品牌名称。
Apache 站点指向已经确认的新文件:
SSLCertificateFile /受控证书目录/fullchain.pem
SSLCertificateKeyFile /受控证书目录/privkey.pem然后检查配置,成功才重载:
sudo apache2ctl configtest && sudo systemctl reload apache2配置失败时恢复备份,不反复重启服务碰运气。公网验证不使用 -k:
DOMAIN='example.com'
curl -sS -o /dev/null \
-w 'http=%{http_code} verify=%{ssl_verify_result}\n' "https://$DOMAIN/"核对 curl 退出码,并从干净、可信的证书存储环境测试域名校验;浏览器无痕模式不会自动清除系统信任的根证书。多入口站点还要逐个核对实际提供的证书,不只看一次域名访问。
续期由谁负责
异机签发不意味着证书必然不能自动续期,关键是在哪台机器续期、续期后如何送到入口。可以选择两种方式:
| 方式 | 必须补齐 |
|---|---|
| 签发机长期负责 | 保留其 Certbot 状态、调度和 DNS 权限;成功续期后安全分发,入口检查并重载 |
| 安装机接管 | 恢复网络后,用自己的受控配置完成签发与续期验证,再切换站点证书引用 |
只在安装机放入两个 PEM 文件,不会让它的 Certbot 自动管理这张证书。反过来,续期配置文件存在,也不等于定时任务或部署钩子正常。
在实际负责续期的机器上检查:
# CERT_NAME 使用列表中的实际证书名称,不一定等于单个域名
certbot certificates
: "${CERT_NAME:?请先设置实际证书名称}"
certbot renew --cert-name "$CERT_NAME" --dry-run自定义配置目录时,续期任务也要使用同一目录。dry-run 会访问测试 ACME 服务,并可能真实修改挑战 TXT;它不是完全无副作用,也不能证明生产端点一定可达。生产和测试端点分别判断,测试证书不部署到生产。详见 Certbot 续期说明。
再检查调度、执行用户、PATH、日志和成功续期后的部署钩子。定时任务不会自动继承交互终端环境;“已签出新证书但重载失败”时,要查看真实退出码和错误,不只搜成功提示。
不要写死“90 天有效、提前 30 天续期”,以证书实际有效期和客户端策略为准,并对外部可见证书设置到期告警。完成接管后,确认续期、安装及公网验证都通过,再撤销临时任务、清理无引用且不再需要回退的文件。旧私钥也不会因为换了证书就自动失效。
完成检查
- 已按日志区分出站连接与挑战验证问题,没有只靠关键字下结论。
- 签发成功,域名覆盖、密钥配对和公网证书验证通过。
- 明确负责续期的机器,调度、挑战权限和部署链路都有验证结果。
- 失败时有可用配置回退点,临时方案设有截止时间和告警。
遇到暂时故障可以受控重试,但要遵守限流和退避。先确认方向再处理,比反复申请正式证书更有效。

暂无评论