Let's Encrypt:「无法获取颁发者证书」(错误 2),强制重新签发及速率限制

Discourse 的安装或重建可能会拒绝有效的 Let’s Encrypt 证书,并出现以下错误:

C=US, O=Let's Encrypt, CN=YR2
error 2 at 1 depth lookup: unable to get issuer certificate
error fullchain.cer: verification failed

我在 RSA 证书成功签发后立即复现了此问题。失败的检查会触发不必要的 --force 请求,这可能导致 HTTP 429 / rateLimited / “too many certificates”(证书过多)错误。在我的安装环境中,随后 ECDSA 证书的签发被阻止,HTTPS 未能启动。

对于受影响的证书链,每次重建或容器重启都可能触发 RSA 和 ECDSA 证书的不必要强制重新签发,即使这些证书仍然有效。成功的重新签发会消耗 Let’s Encrypt 的配额,因此反复重建或重启可能会迅速达到速率限制。

如果 HTTPS 继续使用已安装的证书正常工作,这种情况可能未被察觉。删除并使用相同主机名重新安装实例并不会重置该配额。如果新安装中缺少所需的证书,受速率限制的签发可能会阻止 HTTPS 启动。

修复方案

在 templates/web.letsencrypt.ssl.template.yml 文件中,cert_exists() 函数内部,请替换以下内容:

- openssl verify -CAfile <(openssl x509 -in ca.cer) fullchain.cer
+ openssl verify -untrusted ca.cer fullchain.cer

保持该函数的其余部分不变。修正后的模板必须包含在下一次容器重建中,以更新 /usr/local/bin/letsencrypt。

如果您已经受到速率限制,补丁不会重置配额。请应用此修复,并尊重 retry after(重试后)时间,然后再尝试进一步的签发。

PR: FIX: preserve the Let's Encrypt issuer chain in cert_exists - Pull Request #1136 - discourse/discourse_docker - GitHub

确认证书链验证问题

在容器内受影响的证书目录中(/shared/letsencrypt/<hostname> 或 <hostname>_ecc),请比较以下命令的输出:

openssl verify -CAfile <(openssl x509 -in ca.cer) fullchain.cer
openssl verify -untrusted ca.cer fullchain.cer

在复现的案例中,第一个命令以错误 2 失败;第二个命令返回:

fullchain.cer: OK

原因与验证

openssl x509 -in ca.cer 仅从颁发者捆绑包中读取第一个证书。在观察到的 YR2 链中,它丢弃了到达 ISRG Root X1 所需的交叉签名 Root YR。

-untrusted ca.cer 提供了完整的颁发者捆绑包,同时将信任锚保留在系统存储中,从而保留了 2021 年修复 (#576) 的初衷。

我在容器内验证了实际的 RSA 和 ECDSA 证书链。使用补丁进行完全重建后,复用了两个现有证书:出现两条 Skip 消息,两个证书链均已安装,且 nginx -t 测试通过。

相关报告

411097
413078

PR: FIX: preserve the Let's Encrypt issuer chain in cert_exists - Pull Request #1136 - discourse/discourse_docker - GitHub

5 个赞

请在此处发布您的有效证书及证书链,以便我们进行评估。

2 个赞

感谢 @supermathie 对此事的调查。我已附上受影响实例中的 fullchain.cer 和 ca.cer 文件。
fullchain.cer.txt (5.9 KB)
ca.cer.txt (3.8 KB)

下方的 OpenSSL 输出显示,原始的验证命令以错误 2 失败,而使用相同的证书和证书链时,添加 -untrusted 参数则验证成功。

openssl version
openssl verify -CAfile <(openssl x509 -in ca.cer) fullchain.cer
openssl verify -show_chain -untrusted ca.cer fullchain.cer

OpenSSL 3.5.7 9 Jun 2026 (Library: OpenSSL 3.5.7 9 Jun 2026)
C=US, O=Let's Encrypt, CN=YR2
error 2 at 1 depth lookup: unable to get issuer certificate
error fullchain.cer: verification failed
fullchain.cer: OK
Chain:
depth=0: CN=hubprod.surikai.fr (untrusted)
depth=1: C=US, O=Let's Encrypt, CN=YR2 (untrusted)
depth=2: C=US, O=ISRG, CN=Root YR (untrusted)
depth=3: C=US, O=Internet Security Research Group, CN=ISRG Root X1

我在两个独立的环境中遇到了这个问题。自提交 PR 以来,我一直在开发实例上运行该修复方案,目前运行符合预期。

1 个赞

供参考,ACME 客户端会在 ${LETSENCRYPT_DIR} 中创建以下相关文件:

  • ${DISCOURSE_HOSTNAME}.cer:包含已签发的证书
  • ca.cer:包含证书链,其命名略显令人困惑
  • fullchain.cer:合并了上述两个文件

最正确的命令应为:

openssl verify -untrusted ca.cer ${DISCOURSE_HOSTNAME}.cer

当前的命令一团糟(而且并不正确,因为它实际上并未验证证书链是否有效直至系统根证书),其他任何做法都依赖于副作用或假设。我会更新该 PR。

1 个赞

有道理。直接验证叶证书比依赖 OpenSSL 只完整读取 fullchain.cer 中的第一个证书更简洁。

我会关闭 PR #1136,以支持你的 PR。

如有需要,我很乐意测试新的 PR。