- 云原生
- 运维
【免费下载链接】acme-companion
Automated ACME SSL certificate generation for nginx-proxy
本文基于 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 的配置方式之前,先看它在源码中的完整传递链路。这条链路分为三步:
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 位。服务脚本 source 生成的数据文件。letsencrypt_service.sh 在启动时
source /app/letsencrypt_service_data,把这些按容器隔离的变量载入当前 shell 环境。组装 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}") fiPost-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 的编码约定。
测试同时验证了两个层面的行为:
- 默认 Hook 生效:在 acme-companion 容器上设置
ACME_PRE_HOOK=touch /tmp/default_prehook与ACME_POST_HOOK=touch /tmp/default_posthook(第 20-22 行),签发完成后检查 conf 中 base64 编码是否正确、且容器内确实生成了对应文件(第 82-87 行); - 每容器 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 的典型落地场景包括:
- 临时调整防火墙规则:ACME 的 HTTP-01 挑战通常要求 80 或 443 端口对 CA 的验证服务器公开可达。可以在 Pre-Hook 中临时放行对应端口(例如通过 curl 调用防火墙管理 API),在 Post-Hook 中再关闭,从而不必让 80/443 长时间对外开放。
- 证书"后处理"与格式转换:签发完成后,在 Post-Hook 中把 PEM 证书转换成应用需要的格式(如 PKCS#12、DER),或拷贝到指定位置,供非 HTTPS 场景(如 FTPS、邮件服务器)使用。配合
ACME_RESTART_CONTAINER(见 Let's-Encrypt-and-ACME.md)还可以在续期后重启相关容器加载新证书。 - 监控:在 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
相关推荐
acme-companion 实战指南:为 nginx-proxy 自动化签发与续期 ACME SSL 证书
acme companion 实战指南:为 nginx proxy 自动化签发与续期 ACME SSL 证书 本文围绕 nginx proxy 生态中的轻量级伴
云原生运维letsencrypt.sh 钩子脚本:如何自定义证书签发和部署流程
letsencrypt.sh 钩子脚本:如何自定义证书签发和部署流程 letsencrypt.sh(也称为 dehydrated)是一个轻量级的ACME客户端,
网络安全运维acme-companion 独立证书(Standalone Certificates)配置指南:通过 `/app/letsencrypt_user_data` 为无容器依赖场景签发 ACME 证书
acme companion 独立证书(Standalone Certificates)配置指南:通过 /app/letsencrypt_user_data 为
云原生运维
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考