☰
acme-companion 证书签发钩子完全指南:用 ACME_PRE_HOOK / ACME_POST_HOOK 在证书签发前后执行自定义动作
2026/9/27 10:18:53 网站建设 项目流程
  • 云原生
  • 运维

【免费下载链接】acme-companion

Automated ACME SSL certificate generation for nginx-proxy

项目地址:https://gitcode.com/gh_mirrors/ac/acme-companion
点击查看免费下载

本文基于 acme-companion 官方文档 Hooks.md,深入讲解如何通过ACME_PRE_HOOK与ACME_POST_HOOK环境变量,在 ACME 证书签发前、后自动执行自定义命令。文章结合 letsencrypt_service.sh 源码与 acme_hooks 集成测试,说明其底层实现原理、默认/每容器两种配置方式、优先级规则、验证方法与实际限制。读完本文,你将能够在 nginx-proxy + acme-companion 的自动 HTTPS 体系中,灵活接入防火墙临时放行、证书格式转换、监控告警等自动化动作。

什么是 Pre-Hook 与 Post-Hook

acme-companion 本质上是 nginx-proxy 的 ACME 证书自动签发伴生容器,它把 Docker 容器的环境变量翻译成 acme.sh 的调用参数。acme.sh 本身提供了 Pre-Hook、Post-Hook(以及 Renew-Hook、ReloadCmd)机制,允许在证书签发流程的特定时间点执行用户命令。acme-companion 通过两个环境变量将其暴露给用户:

  • ACME_PRE_HOOK:在证书签发之前执行的命令;
  • ACME_POST_HOOK:在证书签发之后执行的命令。

典型应用包括:仅在 ACME 授权期间临时调整防火墙规则、签发完成后对证书做"后处理"(如格式转换)、以及接入监控系统。关于 acme.sh 原生的 hook 能力,可参考 acme.sh 官方 Wiki 的 "Using pre-hook post-hook renew-hook reloadcmd" 文档;在 acme-companion 项目中,相关入口文档见 Let's-Encrypt-and-ACME.md 与 Container-configuration.md。

底层实现:环境变量如何变成 acme.sh 参数

理解 hook 的配置方式之前,先看它在源码中的完整传递链路。这条链路分为三步:

  1. docker-gen 模板采集容器环境变量。letsencrypt_service_data.tmpl 中通过coalesce $container.Env.ACME_PRE_HOOK ""和coalesce $container.Env.ACME_POST_HOOK ""从每个被代理容器的环境变量中读取 hook 命令,并 trim 掉首尾空白;随后将结果写进生成的/app/letsencrypt_service_data,对应变量名为ACME_${cid}_PRE_HOOK与ACME_${cid}_POST_HOOK(见该模板第 87-88、122-123 行)。这里的cid是容器 ID 的前 12 位。

  2. 服务脚本 source 生成的数据文件。letsencrypt_service.sh 在启动时source /app/letsencrypt_service_data,把这些按容器隔离的变量载入当前 shell 环境。

  3. 组装 acme.sh --issue 参数。签发函数中(letsencrypt_service.sh)通过local -n acme_pre_hook="ACME_${cid}_PRE_HOOK"间接引用每容器变量,再拼接到params_issue_arr:

# acme.sh pre and post hooks local -n acme_pre_hook="ACME_${cid}_PRE_HOOK" if [[ -n "${acme_pre_hook}" ]]; then # Use per-container pre hook params_issue_arr+=(--pre-hook "${acme_pre_hook}") elif [[ -n ${ACME_PRE_HOOK// } ]]; then # Use default pre hook params_issue_arr+=(--pre-hook "${ACME_PRE_HOOK}") fi

Post-Hook 的处理逻辑完全相同,最终以--pre-hook/--post-hook参数随acme.sh --issue(第 532 行)一起执行。值得注意:--pre-hook参数值会原样传递给 acme.sh,因此 hook 中涉及引号、空格等特殊字符时,需要按照 shell 命令的书写习惯在环境变量值中正确转义。

默认 Hook:设置在 acme-companion 容器上

如果把ACME_PRE_HOOK/ACME_POST_HOOK设置在acme-companion容器上,那么所有证书的签发都会执行相同的动作。例如在 acme-companion 容器上设置默认 Pre-Hook(签发前执行echo 'start'):

$ docker run --detach \ --name nginx-proxy-acme \ --volumes-from nginx-proxy \ --volume /var/run/docker.sock:/var/run/docker.sock:ro \ --volume acme:/etc/acme.sh \ --env "DEFAULT_EMAIL=mail@yourdomain.tld" \ --env "ACME_PRE_HOOK=echo 'start'" \ nginxproxy/acme-companion

设置默认 Post-Hook(签发后执行echo 'end'):

$ docker run --detach \ --name nginx-proxy-acme \ --volumes-from nginx-proxy \ --volume /var/run/docker.sock:/var/run/docker.sock:ro \ --volume acme:/etc/acme.sh \ --env "DEFAULT_EMAIL=mail@yourdomain.tld" \ --env "ACME_POST_HOOK=echo 'end'" \ nginxproxy/acme-companion

上面的示例沿用了 Hooks.md 的标准启动参数:--volumes-from nginx-proxy复用 nginx-proxy 的卷,/var/run/docker.sock以只读方式挂载供 docker-gen 监听容器事件,acme:/etc/acme.sh持久化证书与 acme.sh 数据。如果你使用 Docker Compose,也可以把ACME_PRE_HOOK/ACME_POST_HOOK直接写进 acme-companion 服务的environment小节,效果等同。

每容器 Hook:只对特定证书生效

如果希望不同证书执行不同的动作,就把ACME_PRE_HOOK/ACME_POST_HOOK设置在被代理的容器上。例如对某个代理应用容器设置每容器 Pre-Hook:

$ docker run --detach \ --name your-proxyed-app \ --env "VIRTUAL_HOST=yourdomain.tld" \ --env "ACME_HOST=yourdomain.tld" \ --env "ACME_PRE_HOOK=echo 'start'" \ nginx

对另一个应用容器设置每容器 Post-Hook:

$ docker run --detach \ --name your-proxyed-app \ --env "VIRTUAL_HOST=yourdomain.tld" \ --env "ACME_HOST=yourdomain.tld" \ --env "ACME_POST_HOOK=echo 'start'" \ nginx

注意:这些命令是在acme-companion 容器内部执行的,而非在被代理容器内执行。ACME_HOST用于指定该容器需要证书的域名(兼容旧变量名LETSENCRYPT_HOST),VIRTUAL_HOST则是 nginx-proxy 路由所需的域名,二者通常一致。

优先级与组合规则(重要)

  • 不合并:默认(设置在 acme-companion 容器上)与每容器的ACME_PRE_HOOK/ACME_POST_HOOK不会叠加执行。当某个被代理容器同时存在默认值和每容器值时,每容器值优先生效,默认值被完全忽略。这一优先级逻辑同样体现在 letsencrypt_service.sh 中:先判断ACME_${cid}_PRE_HOOK是否非空,非空即采用,否则才回落到全局ACME_PRE_HOOK。
  • 例外容器禁用默认 Hook:如果大多数容器想用同一套默认 Hook,但个别容器不想执行,可以在这几个容器上把变量值设为 Bash 的 noop 操作符:(即ACME_PRE_HOOK=:)。由于每容器值非空时优先于默认值,:这个"什么都不做"的合法命令就覆盖掉了全局默认,达到只对特定容器禁用 Hook 的效果。同理可设置ACME_POST_HOOK=:。

如何验证 Hook 是否真正生效

文档给出了直接的验证手段:检查 acme.sh 在容器内持久化的域名配置文件/etc/acme.sh/[EMAILADDRESS]/[DOMAIN]/[DOMAIN].conf。其中:

  • 变量Le_PreHook存放 Pre-Hook 命令,base64 编码;
  • 变量Le_PostHook存放 Post-Hook 命令,base64 编码。

acme-companion 仓库中的集成测试 test/tests/acme_hooks/run.sh 对该行为做了精确断言,并揭示了编码细节:测试期望配置文件中出现形如Le_PreHook='__ACME_BASE64__START_<base64内容>__ACME_BASE64__END_'的内容(第 53-56 行),再通过echo -n "${command}" | base64计算出期望值做比对(第 67-78 行)。也就是说,实际写入 conf 的 base64 串还带有一对__ACME_BASE64__START_/__ACME_BASE64__END_包裹标记,这是 acme.sh 的编码约定。

测试同时验证了两个层面的行为:

  1. 默认 Hook 生效:在 acme-companion 容器上设置ACME_PRE_HOOK=touch /tmp/default_prehook与ACME_POST_HOOK=touch /tmp/default_posthook(第 20-22 行),签发完成后检查 conf 中 base64 编码是否正确、且容器内确实生成了对应文件(第 82-87 行);
  2. 每容器 Hook 生效:为第二个被代理容器单独设置 hook 命令(第 44-48 行),同样检查 conf 编码与文件落盘(第 99-119 行),证明每容器配置独立生效。

这套测试恰好对应文档中"默认/每容器"两种配置模式的正确性验证,如果你想在自己的环境里手动核对,可以在容器内执行:

$ docker exec nginx-proxy-acme \ grep Le_PreHook "/etc/acme.sh/contact@yourdomain.tld/yourdomain.tld/yourdomain.tld.conf"

然后将取回的 base64 值解码,确认与你设置的命令一致(注意剥掉__ACME_BASE64__START_/__ACME_BASE64__END_标记)。

限制:命令只能使用容器内可用的工具

Hook 命令在acme-companion 容器内部执行,因此命令的选择受限于容器镜像内已安装的工具,不能假定所有系统命令都可用。文档明确指出curl和wget是可用的,因此可以通过 HTTP 与容器外部的工具或其它容器通信,把复杂动作放到外部实现。例如:

  • 调用外部 HTTP 接口触发防火墙规则变更或撤销变更;
  • 向监控系统(自建 Webhook 等)推送证书签发/续期事件。

设计 Hook 时,建议先docker exec nginx-proxy-acme which <command>确认命令存在,或直接选用文档确认可用的curl/wget,避免因缺少二进制导致 Hook 静默失败。

典型使用场景

结合 Hooks.md 与项目上下文,Pre/Post-Hook 的典型落地场景包括:

  1. 临时调整防火墙规则:ACME 的 HTTP-01 挑战通常要求 80 或 443 端口对 CA 的验证服务器公开可达。可以在 Pre-Hook 中临时放行对应端口(例如通过 curl 调用防火墙管理 API),在 Post-Hook 中再关闭,从而不必让 80/443 长时间对外开放。
  2. 证书"后处理"与格式转换:签发完成后,在 Post-Hook 中把 PEM 证书转换成应用需要的格式(如 PKCS#12、DER),或拷贝到指定位置,供非 HTTPS 场景(如 FTPS、邮件服务器)使用。配合ACME_RESTART_CONTAINER(见 Let's-Encrypt-and-ACME.md)还可以在续期后重启相关容器加载新证书。
  3. 监控:在 Post-Hook 中上报签发结果、证书有效期等指标,便于巡检证书是否按时续期成功。

相关配置速查

  • ACME_PRE_HOOK:证书签发前执行的命令;可设置在 acme-companion(全局默认)或被代理容器(每容器,优先)。
  • ACME_POST_HOOK:证书签发后执行的命令;设置方式与优先级同上。
  • ACME_${cid}_PRE_HOOK/ACME_${cid}_POST_HOOK:docker-gen 生成的每容器内部变量(见 letsencrypt_service_data.tmpl),一般无需手动设置,仅用于理解实现。
  • 两个变量的完整环境变量参考见 Environment-variables-reference.md,与其它 ACME 相关变量的关系见 Let's-Encrypt-and-ACME.md。

小结

ACME_PRE_HOOK/ACME_POST_HOOK是 acme-companion 把 acme.sh 的 Hook 能力透传给用户的两个关键开关:设置在 acme-companion 容器上即全局生效,设置在某个被代理容器上则只影响该容器的证书,且每容器值永远优先、不会与默认值合并;想豁免个别容器时可用 Bash noop 操作符:覆盖。Hook 命令在 acme-companion 容器内执行,受容器可用工具限制(curl、wget可用),可通过 letsencrypt_service.sh 源码与 acme_hooks 集成测试 印证其实现与验证方式。掌握这套机制后,你可以在完全自动化的证书生命周期中插入任意自定义动作,让 HTTPS 证书管理真正贴合你的运维流程。

  • 云原生
  • 运维

【免费下载链接】acme-companion

Automated ACME SSL certificate generation for nginx-proxy

项目地址:https://gitcode.com/gh_mirrors/ac/acme-companion
点击查看免费下载
上一篇:Prettier 韩文(Hangul)Markdown 格式化解析:`splitCjkText/korean.md` 测试用例深度解读
下一篇:SMAPI安卓安装器终极指南:5分钟快速配置星露谷物语模组环境

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询