- 网络安全
- 运维
【免费下载链接】dehydrated
ACME client implemented as a simple shell-script – just add water
导读
dehydrated 是一款以纯 Bash 脚本实现的 ACME 客户端(当前仓库版本为 0.7.3),用于向 Let's Encrypt 等 CA 自动申请与续期 TLS 证书。本文以仓库内官方文档 docs/troubleshooting.md 为骨架,逐条拆解用户在部署与续期过程中最常见的报错——包括"账户注册不匹配"、"证书数量超限"、"挑战验证失败"与 DNS 缓存冲突——并结合 dehydrated 主脚本的源码实现,给出可复现的排查路径与修复方案。读完本文,你将能独立定位:账户密钥何时需要重建、WELLKNOWN 目录为何必须可读、DNS 挑战为何要先部署后验证,以及单 TXT 记录限制下的务实变通手段。
排查前请先确认:本文所有命令均针对仓库当前版本
dehydrated 0.7.3,且建议先在 docs/staging.md 描述的预演环境(staging CA)中复现问题,避免在正式环境反复触发 CA 速率限制。
排查起点:先搜 Issue,再建新单
文档在开篇就给出了一条社区协作准则:如果下文的信息没有解决你的问题,在提交新 Issue 之前,请先搜索已有 Issue(用关键词检索)。这是因为大部分"疑难杂症"在脱水项目的 issue 追踪器中往往已有讨论与临时 workaround,直接提交重复问题既浪费维护者精力,也无法更快获得答案。作为自动化运维场景的补充,你还可以结合 hook 机制——docs/examples/hook.sh 中提供了invalid_challenge、request_failure等回调,可以在验证失败或 HTTP 请求出错时自动通知管理员(例如通过sendmail),实现"故障自动上报、人工介入排查"的闭环。
账户注册异常:"No registration exists matching provided key"
报错含义与根因
当 dehydrated 报出No registration exists matching provided key时,文档明确指出:你很可能从 staging CA 切换到了生产 CA(或反向切换)。ACME 协议中,账户(registration)是绑定到特定 CA 服务端的:你在 staging 环境注册的账户密钥,在 production 环境并不存在对应注册记录。
从源码看,dehydrated 目前**不会主动检测"当前 CA 上缺失注册"**这一情况——dehydrated 中注册流程会检查CA_NEW_REG/CA_NEW_ACCOUNT是否配置,但并未对"密钥已存在但注册缺失"做预判,因此需要人工干预。
官方 workaround:移开旧密钥强制重建
当前的标准解法是:将private_key.pem(以及如有必要private_key.json)移出原位置,让脚本重新生成并注册新账户密钥:
# 先备份,而非删除(保留审计与回滚能力) mv private_key.pem private_key.pem.bak mv private_key.json private_key.json.bak # 如有该文件一并移开 ./dehydrated --register --accept-terms源码层面的佐证与延伸
从 dehydrated 可以看到,脚本在初始化阶段会自动完成一次"旧版路径迁移":
if [[ -f "${BASEDIR}/private_key.pem" ]] && [[ ! -f "${ACCOUNT_KEY}" ]]; then echo "! Moving private_key.pem to ${ACCOUNT_KEY}" mv "${BASEDIR}/private_key.pem" "${ACCOUNT_KEY}" fi if [[ -f "${BASEDIR}/private_key.json" ]] && [[ ! -f "${ACCOUNT_KEY_JSON}" ]]; then echo "! Moving private_key.json to ${ACCOUNT_KEY_JSON}" mv "${BASEDIR}/private_key.json" "${ACCOUNT_KEY_JSON}" fi即:老版本遗留的BASEDIR/private_key.pem与private_key.json会被自动迁移到按 CA 哈希命名的账户目录accounts/<CAHASH>/account_key.pem与registration_info.json。因此如果你的报错出现在升级 dehydrated 之后,先检查accounts/<CAHASH>/下是否已存在account_key.pem——若存在说明迁移已发生,问题根源更可能是CA 切换而非路径丢失。
此外 dehydrated 展示了多 CA 并存时的账户目录设计:每个 CA 端点经 urlbase64 哈希后形成独立子目录;当检测到旧 CA(OLDCA,默认指向https://acme-v01.api.letsencrypt.org/directory,见 docs/examples/config 的OLDCA注释)下已有账户时,会以符号链接复用该账户密钥。这意味着:
- 在同一 CA 家族(如 LE v01 → v02)间升级时,脚本会尽量复用旧账户;
- 而在 staging ↔ production 这类端点完全不同的场景下,复用逻辑失效,必须按上述 workaround 手动重建。
文档同时注明:"This will hopefully be fixed in the future"——即自动检测缺失注册属于已知待办,目前只能依赖手动处理。
CA 侧限制类报错:证书数量与 SAN 上限
"Error creating new cert :: Too many certificates already issued for: [...]"
这个报错不是 dehydrated 的问题,而是 boulder(ACME 服务器)的 API 速率限制。文档给出当时(写作时点)的数值:
每个域名在7 天滑动窗口内最多签发5 张证书。
这是 Let's Encrypt 侧众所周知的"5/7"限频(Certificates per Domain),适用于所有 ACME 客户端,并非 dehydrated 特有。排查与规避建议:
- 若在同一域名上频繁执行
--force强制重签(例如测试 CSR 生成逻辑),极易撞上该限制; - 生产环境应依赖 dehydrated 的到期前自动续期机制(默认
RENEW_DAYS="32",见 docs/examples/config),而非手动反复签发; - 若确实需要大量测试,请在 staging 环境进行——staging CA 的速率限制宽松得多。
"Certificate request has 123 names, maximum is 100."
同样是 boulder 的限制:单张证书最多包含 100 个域名(SAN)。当你的domains.txt中某条记录聚合了超过 100 个域名时就会触发。dehydrated 的域名清单语法见 docs/domains_txt.md,一个典型的超限场景是把整个组织域名全部塞进一行。解法是把域名拆分成多个证书条目(每行一个证书,SAN 数控制在 100 以内)。由于 docs/examples/config 显示DOMAINS_TXT="${BASEDIR}/domains.txt"可自由指定,你可以为不同业务域维护多份清单文件。
关于限频数值的时效性提示
上述"5 张/7 天"与"100 个 SAN"是文档撰写时的 boulder/Let's Encrypt 默认值,CA 侧策略可能随时间调整。遇到此类报错时,应以 ACME 服务器返回的错误 detail 字段为准(dehydrated 会通过_exiterr原样输出),并关注 CA 官方公告。
HTTP-01 挑战无效:WELLKNOWN 可读性排查
报错场景
使用默认的http-01验证(配置CHALLENGETYPE="http-01",见 docs/examples/config)时,ACME 服务器会通过http://example.org/.well-known/acme-challenge/<token>访问验证文件。挑战无效的常见诱因包括:WELLKNOWN 路径配置错误、Web 服务器未对该路径做别名/路由、防火墙或反代拦截等。
官方推荐的自测方法
文档给出了一个非常实用的验证步骤——在 WELLKNOWN 目录放一个测试文件,然后从公网浏览器访问:
# 1. 在 WELLKNOWN 目录创建测试文件 touch "${WELLKNOWN}/test.txt" # 默认 WELLKNOWN=/var/www/dehydrated # 2. 在浏览器/curl 中打开 curl -v http://example.org/.well-known/acme-challenge/test.txt关键注意点(文档特别强调):
- 若你的域名有IPv6 地址(AAAA 记录),ACME 挑战连接将走IPv6;
- 仅用浏览器测试往往不够——因为浏览器在 IPv6 失败时通常会静默回退到 IPv4(Happy Eyeballs),从而掩盖 IPv6 通道的问题;
- 因此必须分别验证 IPv4 与 IPv6 两条链路:
curl -4与curl -6各测一次; - 任何一条链路返回错误,都需要修复 Web 服务器配置。
源码对照:挑战文件如何写入
从 dehydrated 可以看到 http-01 的写入逻辑:
"http-01") # Store challenge response in well-known location and make world-readable (so that a webserver can access it) printf '%s' "${keyauth}" > "${WELLKNOWN}/${challenge_tokens[${idx}]}" chmod a+r "${WELLKNOWN}/${challenge_tokens[${idx}]}" keyauth_hook="${keyauth}" ;;注意两个细节:
- 文件会被
chmod a+r设置为全局可读,以让 Web 服务器(可能是其他用户运行)能读取; - 挑战验证完成后脚本会自动清理:验证循环里对 http-01 执行
rm -f "${WELLKNOWN}/${challenge_tokens[${idx}]}"(dehydrated)。
多 docroot / 反向代理场景的配置模板
如果你的服务器只有单一 docroot,直接设WELLKNOWN=/var/www/.well-known/acme-challenge即可。但在多 docroot、反向代理或负载均衡架构下,更稳妥的做法是:创建独立目录/var/www/dehydrated,在配置中设置WELLKNOWN=/var/www/dehydrated(默认值即是此路径,见 docs/examples/config),再为各 Web 服务器配置别名。完整配置模板见 docs/wellknown.md,以下为四种服务器的核心片段:
Nginx(加到每个server块):
server { [...] location ^~ /.well-known/acme-challenge { alias /var/www/dehydrated; } [...] }Apache 2.x / 2.4(全局或 VHost):
Alias /.well-known/acme-challenge /var/www/dehydrated <Directory /var/www/dehydrated> Options None AllowOverride None # Apache 2.x <IfModule !mod_authz_core.c> Order allow,deny Allow from all </IfModule> # Apache 2.4 <IfModule mod_authz_core.c> Require all granted </IfModule> </Directory>Lighttpd(启用 alias 模块):
server.modules += ("alias") alias.url += ( "/.well-known/acme-challenge/" => "/var/www/dehydrated/", )Hiawatha(每个 VirtualHost 内):
VirtualHost { Hostname = example.tld subdomain.mywebsite.tld Alias = /.well-known/acme-challenge:/var/www/dehydrated }其他常见诱因清单
WELLKNOWN目录权限不足:确保 Web 服务器进程对目录有读权限、对目录内文件有读权限(脚本写入时已chmod a+r);- HTTP→HTTPS 跳转:文档指出挑战起始点永远是 HTTP(端口 80),允许重定向到 HTTPS,但入口必须是 80 端口且能访问到验证文件;
- 反向代理/负载均衡未将
/.well-known/acme-challenge转发到实际提供文件的节点; - IP 版本问题:按上文用
curl -4/curl -6分别验证。
DNS-01 挑战:为何"先全部部署、后逐个验证"(dehydrated 0.6.0+ 行为)
问题背景:通配符与 DNS 缓存
自 Let's Encrypt 支持通配符域名(ACMEv2)以来,出现了一个 DNS 缓存相关的陷阱:如果一张证书同时包含example.org与*.example.org,则需要在_acme-challenge.example.org上同时部署两个不同的 token。若 dehydrated 逐个"部署→验证→再部署下一个",CA 会缓存第一个 token,导致第二个挑战直接失败。
文档给出的 CA 侧缓存规则:Let's Encrypt 使用你的 DNS TTL,但上限为 5 分钟——这并非 ACME 协议的一部分,而是 LE 特有的配置;其他 CA 与某些不允许低 TTL 的 DNS 服务商组合下,缓存失效可能长达数小时。
行为变更:dehydrated 0.6.0 起先部署后验证
从 dehydrated 0.6.0 开始,脚本改为先将所有挑战一次性部署,再逐个请求 CA 验证,从而让 CA 能一次性查询并缓存全部 TXT 记录,两个授权都能成功验证。源码印证(dehydrated):
# Deploy challenge tokens if [[ ${num_pending_challenges} -ne 0 ]]; then if [[ "${CHALLENGETYPE}" != "dns-persist-01" ]]; then echo " + Deploying challenge tokens..." if [[ -n "${HOOK}" ]] && [[ "${HOOK_CHAIN}" = "yes" ]]; then # shellcheck disable=SC2068 "${HOOK}" "deploy_challenge" ${deploy_args[@]} || _exiterr 'deploy_challenge hook returned with non-zero exit code' elif [[ -n "${HOOK}" ]]; then local idx=0 while [ ${idx} -lt ${num_pending_challenges} ]; do # shellcheck disable=SC2086 "${HOOK}" "deploy_challenge" ${deploy_args[${idx}]} || _exiterr 'deploy_challenge hook returned with non-zero exit code' idx=$((idx+1)) done fi fi fi所有deploy_challenge先执行完毕(默认逐个调用,或配置HOOK_CHAIN="yes"时合并为一次调用,见 docs/examples/config),之后才进入验证循环逐个signed_request并轮询状态(dehydrated)。
对 hook 脚本的兼容性影响
这一变更对 hook 脚本作者有明确要求:有些旧 hook 在部署新 token 时会"先删除旧 TXT 记录"而不是"追加新条目",这类脚本在通配符场景下会把已部署的第一个 token 抹掉,导致验证失败。文档指出这类脚本应(且大多已)被修复——正确做法是deploy_challenge时追加 TXT 记录,clean_challenge时才按 token 精确删除。参考 docs/examples/hook.sh 的deploy_challenge/clean_challenge签名:二者都接收DOMAIN TOKEN_FILENAME TOKEN_VALUE三个参数,dns-01 场景下TOKEN_VALUE即应写入_acme-challenge.<domain>TXT 记录的内容(该值由脚本对 keyauth 做 SHA-256 后 base64url 得到,见 dehydrated)。
单 TXT 记录限制:现实世界中最棘手的坑
文档指出:存在某些 DNS 服务商真的只允许一个域名上有一条 TXT 记录。这相当反常,因为 TXT 记录本身支持多值,ACME 的 dns-01 也依赖多值共存。官方建议按优先级处理:
- 联系 DNS 服务商修复(首选,因为这是服务商侧的能力缺陷);
- 无法更换服务商且对方不修复时,把证书拆分成多张证书,并在
deploy_certhook 中加入sleep,人为错开各证书的挑战部署时间窗口(利用各证书的验证时机差异规避同域双 TXT 冲突); - 若上述都不可行,可在上游 issue #554 留言反馈,维护者视反馈量评估是否实现 workaround。
延伸阅读
- dns-01 挑战的完整 hook 对接说明(参数约定、手动/API 两种部署方式)见 docs/dns-verification.md;
- 通配符证书与多 TXT 记录的实战背景可结合 docs/domains_txt.md 的域名清单语法理解。
附:与排查相关的其他实战参考
- CA 切换与账户迁移:staging ↔ production 切换的完整操作见 docs/staging.md;
- 每个证书独立配置:多证书场景下可为每个证书单独覆盖配置项,见 docs/per-certificate-config.md(源码支持
DOMAINS_D目录加载各证书专属配置,见 docs/examples/config); - IP 证书:若使用 IP 地址作为证书标识,挑战部署参数会有所不同(源码中 ip 类型会转换为 PTR 记录再传给 hook,见 dehydrated),详见 docs/ip-certificates.md;
- TLS-ALPN-01:除 http-01/dns-01 外的第三种挑战类型,其验证证书存放在
ALPNCERTDIR(默认$BASEDIR/alpn-certs),见 docs/tls-alpn.md。
结语
dehydrated 的故障排查核心可归纳为四条主线:账户与 CA 端点的匹配关系(注册异常)、CA 侧限频与数量上限(非脚本缺陷)、挑战路径的可达性(HTTP/DNS 基础设施)、挑战部署时序(先部署后验证)。本文列出的每一步都可对照 dehydrated 源码与 docs/examples/config 默认值逐一验证。若问题仍未解决,请带着报错全文与-x调试输出,在项目 Issue 追踪器中按关键词搜索后提交。
- 网络安全
- 运维
【免费下载链接】dehydrated
ACME client implemented as a simple shell-script – just add water
相关推荐
ALVR 20+ 故障排查完全指南:从启动失败、连接异常到性能调优的实战手册
ALVR 20+ 故障排查完全指南:从启动失败、连接异常到性能调优的实战手册 导读 本文是 ALVR(通过 Wi Fi 将 PC 上的 VR 游戏串流到头显的解
音视频图形学DataHub Quickstart 故障排查完全指南:从 CLI 启动失败到 Docker 容器异常的实战处理
DataHub Quickstart 故障排查完全指南:从 CLI 启动失败到 Docker 容器异常的实战处理 本文围绕 DataHub 官方 Quickst
数据目录数据治理数据血缘后端前端数据工程数据集成Steel Browser 故障排查完全指南:从浏览器启动失败到性能调优的实战手册
Steel Browser 故障排查完全指南:从浏览器启动失败到性能调优的实战手册 Steel Browser 是一套"开箱即用"的浏览器沙箱 API,专为 A
浏览器控制AI 应用
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考