acme.sh 通配符证书验证失败排查复盘一、事件概述1. 事件背景在主机 root@156.226.176.52 上使用 acme.sh 签发 op123.ren 及其多子域通配符证书时，DNS 验证失败，报错提示在 _acme-challenge.n9e.op123.ren 处未找到 TXT 记录。2. 影响范围A. 影响功能无法为以下域名签发或续期证书：op123.ren、.op123.ren、.ngrok.op123.ren、.tools.op123.ren、.atzlinux.op123.ren、.api.op123.ren、.mcp.op123.ren、*.n9e.op123.ren。B. 影响时长自执行命令至根因确认并给出修复方案为止。C. 严重程度证书无法签发会导致上述域名无法使用有效的 HTTPS，属配置类问题，修复后即可恢复。3. 执行命令与报错执行命令为：/root/.acme.sh/acme.sh --issue -d op123.ren -d "*.op123.ren" \ -d "*.ngrok.op123.ren" -d "*.tools.op123.ren" -d "*.atzlinux.op123.ren" \ -d "*.api.op123.ren" -d "*.mcp.op123.ren" -d "*.n9e.op123.ren" \ --dns dns_tencent --challenge-alias sddts.cn --force报错摘要：Invalid status. Verification error details: No TXT record found at _acme-challenge.n9e.op123.renRemoving DNS records 时仅删除了 _acme-challenge.sddts.cn 的 TXT 记录伴随 libcurl error code 3 等次要错误二、事件时间线1. 问题发生用户执行上述 acme.sh 命令，使用腾讯云 DNS API（dns_tencent）与挑战别名（challenge-alias sddts.cn），期望在 sddts.cn 所在 DNS 写入一条 TXT 完成所有域名验证。2. 问题定位通过分析报错与 acme.sh 官方文档可知：使用 --challenge-alias 时，acme.sh 只会把验证用 TXT 写在 _acme-challenge.sddts.cnLet's Encrypt 仍会按域名逐个查询 _acme-challenge.<域名>因此必须在 op123.ren 的 DNS 中，为每个参与签发的（子）域名预先配置 CNAME：_acme-challenge.<该域名> 指向 _acme-challenge.sddts.cn报错指向 _acme-challenge.n9e.op123.ren，说明该名的 CNAME 缺失或指向错误。3. 根因确认用户执行 dig 验证：dig _acme-challenge.n9e.op123.ren CNAME +short返回结果为：_acmhe-challenge.sddts.cn.（多了一个字母 h）。正确目标应为 _acme-challenge.sddts.cn.。CNAME 目标拼写错误，导致 Let's Encrypt 跟随 CNAME 后查询的是 _acmhe-challenge.sddts.cn 的 TXT，而 acme.sh 实际写入的是 _acme-challenge.sddts.cn，因此验证失败。4. 解决方案在 op123.ren 的 DNS 中，将 _acme-challenge.n9e.op123.ren 的 CNAME 目标从 _acmhe-challenge.sddts.cn. 修改为 _acme-challenge.sddts.cn.，等待解析生效后重新执行 acme.sh 签发命令。三、问题分析1. 直接原因Let's Encrypt 在查询 _acme-challenge.n9e.op123.ren 时，通过 CNAME 被指向 _acmhe-challenge.sddts.cn，在该处无法获得 acme.sh 写入的 TXT（TXT 实际在 _acme-challenge.sddts.cn），因此报「No TXT record found at _acme-challenge.n9e.op123.ren」。2. 根本原因（5 Whys 分析）A. 为什么会出现这个问题？CNAME 记录的目标主机名配置错误，写成了 _acmhe-challenge.sddts.cn 而非 _acme-challenge.sddts.cn。B. 为什么没有在首次配置时发现？多子域、多 CNAME 时，易在手工添加或复制时产生拼写错误，且 DNS 配置后未逐条用 dig 校验 CNAME 目标是否正确。C. 为什么其他子域可能未报错？若其他子域（如 op123.ren、ngrok、tools 等）的 _acme-challenge 的 CNAME 已正确指向 _acme-challenge.sddts.cn，则仅 n9e.op123.ren 会验证失败，与报错仅指向 n9e 一致。3. 深层反思使用 DNS alias 模式时，每个域名/子域都依赖一条正确的 CNAME，漏配或写错任一条都会导致该域名验证失败。建议在批量配置 CNAME 后，用脚本或逐条 dig 检查每条 _acme-challenge.<子域> 的 CNAME 是否指向 _acme-challenge.sddts.cn.。四、解决方案1. 临时方案修改 _acme-challenge.n9e.op123.ren 的 CNAME 目标为 _acme-challenge.sddts.cn.，等待 TTL 生效后重跑 acme.sh 命令。2. 永久方案A. 配置清单对使用 --challenge-alias sddts.cn 的证书，确保 op123.ren 的 DNS 中存在以下 CNAME（目标均为 _acme-challenge.sddts.cn.）：主机记录（或等效）类型目标_acme-challengeCNAME_acme-challenge.sddts.cn._acme-challenge.ngrokCNAME_acme-challenge.sddts.cn._acme-challenge.toolsCNAME_acme-challenge.sddts.cn._acme-challenge.atzlinuxCNAME_acme-challenge.sddts.cn._acme-challenge.apiCNAME_acme-challenge.sddts.cn._acme-challenge.mcpCNAME_acme-challenge.sddts.cn._acme-challenge.n9eCNAME_acme-challenge.sddts.cn.B. 验证方法签发前可用下列命令确认 CNAME 是否正确：dig _acme-challenge.n9e.op123.ren CNAME +short # 应返回: _acme-challenge.sddts.cn.3. 预防措施新增子域时，同步在 DNS 中为该子域添加 _acme-challenge.<子域> 的 CNAME 记录。使用文档或清单列出所有需 CNAME 的子域，避免漏配或拼写错误。重要变更后使用 dig 或脚本批量校验 CNAME 目标。五、经验总结1. 做得好的地方报错信息明确指向 _acme-challenge.n9e.op123.ren，便于缩小范围。结合 acme.sh 官方 DNS alias 文档可快速理解「每个域名需独立 CNAME」的机制。通过 dig 直接发现 CNAME 目标拼写错误（acmhe vs acme），定位根因。2. 需要改进的地方初次配置多子域 CNAME 时未逐条校验，导致错误在签发时才暴露。可考虑在文档或运维流程中固化 CNAME 清单与校验步骤。3. 流程优化建议将「acme.sh + challenge-alias」所需 CNAME 列表与校验命令写入内部运维文档或 Runbook。证书续期或新增子域后，增加一次 dig 抽查或脚本校验，确保所有 _acme-challenge 的 CNAME 指向正确。六、参考资料acme.sh DNS alias modeacme.sh 官方仓库
ACME DNS 验证与自定义递归服务器作用分析
ACME DNS 验证与自定义递归服务器作用分析一、问题背景1. 核心疑问在使用 acme.sh 的 DNS 验证方式申请域名证书时，用户可以指定自定义的递归 DNS 服务器。这引发了一个疑问：ACME 服务器是通过互联网的公共 DNS 进行域名验证的，用户指定的 DNS 服务器能起到什么作用？2. 涉及组件ACME 协议：自动化证书管理环境协议acme.sh：Shell 脚本实现的 ACME 客户端DNS 验证：ACME 挑战-响应验证方式之一递归 DNS 服务器：负责递归查询域名解析的服务器二、ACME DNS 验证机制1. 验证流程概述sequenceDiagram participant Client as acme.sh 客户端 participant DNS as DNS 服务商 participant Propagation as DNS 传播 participant ACME as ACME 服务器 (Let's Encrypt) participant PublicDNS as 公共 DNS Client->>DNS: 1. 申请证书 Client->>DNS: 2. 添加 TXT 记录 _acme-challenge.example.com DNS-->>Client: 确认添加成功 Note over Client,ACME: 等待 DNS 传播完成 Client->>PublicDNS: 3. 轮询检测 TXT 记录 (使用指定 DNS 服务器) PublicDNS-->>Client: 仍不可见 Client->>PublicDNS: 继续轮询... PublicDNS-->>Client: 记录已可见 Client->>ACME: 4. 通知 ACME 服务器验证 ACME->>PublicDNS: 5. 从多个位置查询 TXT 记录 PublicDNS-->>ACME: 返回 TXT 记录值 ACME-->>Client: 6. 验证通过，颁发证书 Client->>DNS: 7. 清理 TXT 记录2. ACME 服务器的验证行为A. 多位置验证ACME 服务器（如 Let's Encrypt）从全球多个位置验证 DNS 记录：验证源分布：美国、欧洲、亚太等地使用的 DNS：公共递归服务器（如 Google 8.8.8.8、Cloudflare 1.1.1.1）验证逻辑：必须从多个位置都能查询到正确的 TXT 记录B. 权威性验证路径ACME 服务器 → 公共递归 DNS → 权威 DNS → DNS 记录验证关键点：ACME 服务器不使用用户指定的 DNS 服务器进行验证。三、自定义 DNS 服务器的作用1. 本地预检测机制用户指定的 DNS 服务器仅用于 acme.sh 本地的预检测，不影响 ACME 服务器的验证过程。A. 问题场景时间轴： T0: 用户添加 TXT 记录到 DNS 服务商 T1: DNS 服务商更新权威记录 T2: 记录传播到部分 DNS 服务器 T3: 记录全球可见 acme.sh 在 T2 时刻触发验证 → ACME 服务器可能看不到记录 → 验证失败B. 解决方案# 方式 1：固定等待时间（粗暴） acme.sh --issue --dns dns_cf -d example.com --dnssleep 120 # 方式 2：指定 DNS 服务器进行智能检测 export ACME_DNS_SERVER="8.8.8.8" acme.sh --issue --dns dns_cf -d example.com2. 工作原理对比graph TB subgraph "方式 1：固定等待" A1[添加 TXT 记录] -->|等待 120 秒| B1[触发 ACME 验证] B1 --> C1{ACME 服务器查询} C1 -->|成功| D1[颁发证书] C1 -->|失败| E1[重试] end subgraph "方式 2：智能检测" A2[添加 TXT 记录] -->|轮询检测| B2[使用指定 DNS 查询] B2 -->|未检测到| B2 B2 -->|检测到| C2[触发 ACME 验证] C2 --> D2[颁发证书] end3. 具体配置示例A. 使用公共递归 DNS# 使用 Google DNS 进行预检测 export ACME_DNS_SERVER="8.8.8.8:53" # 使用 Cloudflare DNS export ACME_DNS_SERVER="1.1.1.1:53" # 使用国内 DNS（加速检测） export ACME_DNS_SERVER="223.5.5.5:53"B. 使用权威 DNS# 直接查询权威 DNS 服务器，传播延迟最小 export ACME_DNS_SERVER="ns1.example.com:53"C. acme.sh 源码逻辑# acme.sh 中的检测逻辑（简化） _Dns_lookup() { domain="$1" dns_server="$ACME_DNS_SERVER" # 使用用户指定的 DNS # 如果未指定，使用系统默认 DNS if [ -z "$dns_server" ]; then dns_server=$(cat /etc/resolv.conf | grep nameserver | head -1 | awk '{print $2}') fi # 查询 TXT 记录 dig @$dns_server _acme-challenge.$domain TXT +short } # 轮询检测 _check_dns_propagation() { max_wait=300 # 最多等待 5 分钟 interval=10 # 每 10 秒检测一次 for i in $(seq 1 $((max_wait / interval))); do result=$(_Dns_lookup "$domain") if [ -n "$result" ]; then echo "DNS 记录已传播" return 0 fi echo "等待 DNS 传播... ($i/$((max_wait / interval)))" sleep $interval done return 1 }四、应用场景分析1. 有用的场景A. 加速 DNS 传播检测使用权威 DNS 或就近的递归 DNS 可以更快检测到记录更新：# 场景：DNS 服务商在国内，权威 DNS 在阿里云 export ACME_DNS_SERVER="ns1.aliyun.com:53" acme.sh --issue --dns dns_ali -d example.com效果：无需等待 120 秒固定时间，记录可见后立即触发验证。B. 绕过有问题的本地 DNS某些本地 DNS 可能缓存时间过长或响应异常：# 绕过本地 DNS，直接使用公共 DNS export ACME_DNS_SERVER="8.8.8.8:53"C. 多 DNS 服务器环境在内网有自定义 DNS 服务器时，明确指定使用公共 DNS：# 避免查询到内网的错误结果 export ACME_DNS_SERVER="114.114.114.114:53"2. 无效的场景A. 内网 DNS 验证错误认知：指定内网 DNS 服务器可以让 ACME 服务器验证内网域名 实际情况： - ACME 服务器从互联网查询公共 DNS - 内网 DNS 对 ACME 服务器不可见 - 公网域名无法通过内网 DNS 验证B. 强制 ACME 服务器使用指定 DNS错误认知：用户指定的 DNS 会影响 ACME 服务器的验证行为 实际情况： - ACME 服务器忽略客户端的 DNS 配置 - 使用自己的验证基础设施 - 用户 DNS 仅用于客户端预检测五、技术细节1. DNS 传播时间DNS 服务器类型典型传播时间适用场景权威 DNS0-5 秒最快检测，推荐公共递归 DNS10-60 秒通用场景本地 ISP DNS60-300 秒不推荐，可能缓存旧数据2. 配置参数说明# --dnssleep：固定等待时间（秒） acme.sh --dnssleep 300 # 等待 5 分钟 # ACME_DNS_SERVER：指定 DNS 服务器用于预检测 export ACME_DNS_SERVER="8.8.8.8:53" # 两者关系： # - 设置 ACME_DNS_SERVER 后，--dnssleep 会变成超时时间 # - 未设置 ACME_DNS_SERVER 时，--dnssleep 是固定等待时间3. 最佳实践A. 推荐配置#!/bin/bash # 最佳实践示例 # 1. 使用权威 DNS（如果知道） export ACME_DNS_SERVER="ns1.cloudflare.com:53" # 2. 或使用就近的公共 DNS export ACME_DNS_SERVER="223.5.5.5:53" # 阿里 DNS # 3. 设置合理的超时时间 acme.sh --issue --dns dns_cf \ -d example.com \ -d www.example.com \ --dnssleep 300 # 最多等待 5 分钟B. 生产环境建议对于生产环境，建议等待足够时间（DNS TTL + 缓冲时间）监控 DNS 传播状态，记录实际传播时间使用 DNS 监控服务验证全球可见性六、总结1. 核心要点A. 验证路径分离用户指定 DNS → acme.sh 本地预检测（优化等待时间） ACME 服务器 → 公共 DNS → 权威 DNS（实际验证）B. 作用范围✅ 可以优化：本地检测等待时间❌ 不能改变：ACME 服务器的验证行为❌ 不能实现：内网域名的公网验证C. 推荐使用权威 DNS 服务器：最快检测就近公共 DNS：平衡速度和可靠性避免使用：有缓存问题的本地 ISP DNS2. 常见误解澄清误解实际情况用户 DNS 影响 ACME 验证仅影响本地预检测可以指定内网 DNS 验证ACME 服务器无法访问内网 DNS固定等待时间更可靠智能检测更高效，减少不必要的等待DNS 传播需要很长时间权威 DNS 可立即检测到3. 技术价值自定义 DNS 服务器功能的价值在于：减少证书申请的总时间提高自动化脚本的效率适应不同的网络环境和 DNS 基础设施这是一个典型的 客户端优化 功能，通过本地智能检测减少等待时间，但不改变服务端的验证逻辑。参考资料ACME 协议规范 (RFC 8555)Let's Encrypt 验证方式说明acme.sh 官方文档 - DNS 手动模式DNS 传播机制原理
acme.sh 跨 DNS 服务商多域名证书申请指南一、概述1. 问题背景在实际使用场景中，用户可能拥有多个分散在不同 DNS 服务商的域名。例如：a.com 及其子域名的 DNS 解析托管在阿里云b.com 及其子域名的 DNS 解析托管在腾讯云 DNSPod用户希望生成一张 SSL 证书，同时覆盖 a.com、.a.com、b.com、.b.com 这些域名。2. 核心结论重要提示：acme.sh 官方不支持在单个证书中混合使用多个 DNS 服务商的 API。这是 acme.sh 的设计限制，而非配置问题。二、技术原理1. acme.sh 的工作原理acme.sh 使用 ACME 协议申请证书，支持 DNS-01 验证方式。在 DNS-01 验证中，acme.sh 需要在每个域名的 DNS 解析中添加一条 TXT 记录，证明申请者对该域名的控制权。2. 多 DNS API 的技术限制当申请一个包含多个域名的证书时，acme.sh 会为所有域名使用相同的 DNS API 插件。这是因为：acme.sh 的证书申请命令只接受一个 --dns 参数每个域名的 DNS 验证必须通过该指定的 DNS API 完成如果域名不在该 DNS 服务商处，API 调用将失败graph TD A[acme.sh 申请证书] --> B{指定 DNS API} B -->|阿里云 API| C[验证 a.com] B -->|阿里云 API| D[验证 b.com 失败] C --> E[证书申请失败] D --> E三、解决方案方案一：分别为每个域名生成独立证书（推荐）1. 方案说明为每个 DNS 服务商处的域名分别生成证书，这是最简单、最可靠的方式。2. 阿里云域名证书申请A. 获取阿里云 API 密钥登录阿里云控制台进入访问控制（RAM）-> 用户创建新用户或使用现有用户为用户添加 DNS 管理权限创建 AccessKey，记录 AccessKey ID 和 AccessKey SecretB. 配置环境变量export Ali_Key="your_aliyun_access_key_id" export Ali_Secret="your_aliyun_access_key_secret"C. 申请证书acme.sh --issue --dns dns_ali \ -d a.com \ -d *.a.com3. 腾讯云域名证书申请A. 获取腾讯云 API 密钥登录腾讯云控制台进入访问管理 -> API 密钥管理创建密钥，记录 SecretId 和 SecretKeyB. 配置环境变量export DP_Id="your_tencent_secret_id" export DP_Key="your_tencent_secret_key"C. 申请证书acme.sh --issue --dns dns_dp \ -d b.com \ -d *.b.com4. 优点与缺点优点：配置简单，不易出错证书续期互不影响灵活性高，可独立管理符合最佳实践缺点：需要管理多个证书文件Web 服务器需要配置多个证书方案二：统一 DNS 服务商1. 方案说明将所有域名的 DNS 解析迁移到同一个服务商，然后使用单个证书覆盖所有域名。2. 实施步骤A. 选择目标 DNS 服务商根据需求选择阿里云或腾讯云作为统一 DNS 服务商。B. 迁移 DNS 解析在目标 DNS 服务商添加域名复制所有 DNS 记录到新服务商修改域名 NS 记录，指向新的 DNS 服务器等待 DNS 生效（通常需要 24-48 小时）C. 申请统一证书# 假设全部迁移到阿里云 export Ali_Key="your_aliyun_access_key_id" export Ali_Secret="your_aliyun_access_key_secret" acme.sh --issue --dns dns_ali \ -d a.com \ -d *.a.com \ -d b.com \ -d *.b.com3. 优点与缺点优点：单一证书管理简单减少证书续期任务缺点：需要迁移 DNS 服务迁移过程可能影响服务失去多云冗余优势方案三：DNS 别名模式（不推荐）1. 方案说明acme.sh 支持 DNS 别名模式，但配置复杂，不建议普通用户使用。2. 技术原理DNS 别名模式允许通过 CNAME 记录，将一个域名的 ACME 挑战记录指向另一个域名。这样：a.com 的 ACME 挑战通过阿里云 DNS 验证b.com 的 ACME 挑战通过 CNAME 指向 a.com，间接通过阿里云验证3. 配置复杂度需要手动配置 CNAME 记录需要理解 ACME 协议细节故障排查困难四、最佳实践1. 证书管理建议A. 使用独立证书推荐为每个 DNS 服务商处的域名使用独立证书，这是官方推荐的方式。B. 自动续期配置acme.sh 默认配置自动续期，无需额外操作：# 查看已安装的证书 acme.sh --list # 查看证书续期cron任务 crontab -l | grep acme.shC. 证书部署申请成功后，acme.sh 会自动保存证书，可以通过以下命令部署：acme.sh --install-cert -d a.com \ --cert-file /path/to/cert.pem \ --key-file /path/to/key.pem \ --fullchain-file /path/to/fullchain.pem \ --reloadcmd "service nginx reload"2. 安全建议A. API 密钥权限最小化为 acme.sh 创建专用的 API 密钥，仅授予 DNS 管理权限，避免使用主账号密钥。B. 定期轮换密钥建议每 3-6 个月更换一次 API 密钥。C. 监控证书有效期虽然 acme.sh 会自动续期，但仍建议配置监控告警，确保证书正常续期。3. 故障排查A. 常见问题问题 1：DNS API 验证失败# 检查 API 密钥是否正确 echo $Ali_Key echo $Ali_Secret # 手动测试 DNS API curl "https://alidns.aliyuncs.com/"问题 2：证书申请超时检查网络连接确认 DNS 服务商 API 可用增加等待时间参数：--dnssleep 300问题 3：域名验证失败确认域名已在对应 DNS 服务商处检查 API 密钥是否有该域名的管理权限B. 日志查看# 查看 acme.sh 日志 acme.sh --issue --dns dns_ali -d a.com -d *.a.com --debug五、实际案例案例：Web 服务器多证书配置1. Nginx 配置示例当使用多个证书时，需要在 Nginx 中为每个域名配置对应的证书：# a.com 使用阿里云申请的证书 server { listen 443 ssl; server_name a.com *.a.com; ssl_certificate /path/to/a.com.fullchain.pem; ssl_certificate_key /path/to/a.com.key.pem; # 其他配置... } # b.com 使用腾讯云申请的证书 server { listen 443 ssl; server_name b.com *.b.com; ssl_certificate /path/to/b.com.fullchain.pem; ssl_certificate_key /path/to/b.com.key.pem; # 其他配置... }2. Apache 配置示例 ServerName a.com ServerAlias *.a.com SSLEngine on SSLCertificateFile /path/to/a.com.cert.pem SSLCertificateKeyFile /path/to/a.com.key.pem SSLCertificateChainFile /path/to/a.com.chain.pem ServerName b.com ServerAlias *.b.com SSLEngine on SSLCertificateFile /path/to/b.com.cert.pem SSLCertificateKeyFile /path/to/b.com.key.pem SSLCertificateChainFile /path/to/b.com.chain.pem 六、总结1. 核心要点acme.sh 不支持在单个证书中混合使用多个 DNS API推荐为每个 DNS 服务商处的域名分别生成证书证书自动续期功能由 acme.sh 内置处理多证书配置在 Web 服务器中简单易行2. 推荐方案最佳实践：使用方案一，分别为每个 DNS 服务商申请独立证书。这种方式：配置简单，维护容易符合 acme.sh 的设计理念证书续期互不影响灵活性最高3. 参考资料参考资料acme.sh 官方 GitHub 仓库如何同时使用 CloudFlare 和 DNSPod 的两个域名？Issue #2791acme.sh 支持多个 DNS 服务商讨论 - Let's Encrypt 社区acme.sh 自动解析并申请证书 - DNSPod 官方文档使用 acme.sh 为在腾讯云（DNSPod）解析的域名自动申请证书 - 腾讯云使用 acme.sh 与阿里云 DNS 自动签发免费数字证书 - Kubecc Blogacme.sh 多域名最佳实践教程