☰
acme-companion 独立证书(Standalone Certificates)配置指南:通过 `/app/letsencrypt_user_data` 为无容器依赖场景签发 ACME 证书
2026/9/27 7:20:01 网站建设 项目流程
  • 云原生
  • 运维

【免费下载链接】acme-companion

Automated ACME SSL certificate generation for nginx-proxy

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

导读

本文介绍 acme-companion 的Standalone certificates(独立证书)功能:当你的域名并不对应某个被 nginx-proxy 代理的 Docker 容器时(例如证书要用于宿主机直连的静态站点、非容器化服务、或 nginx-proxy 之外的其他 nginx 实例),你无需为每个证书创建一个"占位容器"并设置ACME_HOST环境变量,只需在容器内挂载一个用户配置文件/app/letsencrypt_user_data,即可为任意域名签发和续期证书。读完本文你将掌握:独立证书的运行前提(卷共享要求)、用户配置文件的语法与全部必选/可选参数、DNS-01 与 HTTP-01 挑战的配置方法、配置变更的拾取机制,以及这些配置在 letsencrypt_service.sh 中对应的底层实现。

为什么需要"独立证书":脱离容器环境变量的证书签发

在 acme-companion 的基本用法中,证书的签发完全由被代理容器上的ACME_HOST环境变量驱动:docker-gen 扫描所有带ACME_HOST(旧称LETSENCRYPT_HOST)的容器,生成/app/letsencrypt_service_data配置文件,acme-companion 再依据该文件为对应域名签发证书(详见 Basic-usage.md)。

但有些场景并不存在"被代理的容器":

  • 证书服务于宿主机上非容器化的 HTTPS 服务;
  • 域名直接解析到宿主机,但流量并不经过 nginx-proxy;
  • 你需要为一个与任何 Docker 容器都无关的域名提前准备证书。

此时仍然可以借助 acme-companion 完成证书的签发与续期,办法就是挂载一个用户配置文件到/app/letsencrypt_user_data。该文件是 bash 变量与数组的集合,语法与 docker-gen 生成的/app/letsencrypt_service_data完全一致——从源码看,update_certs()会先source服务数据文件,再source用户数据文件,两者的变量随后被同一条证书签发链路消费(letsencrypt_service.sh)。

运行前提:必须共享的目录与卷

使用独立证书功能除了挂载用户配置文件外,还要求nginx-proxy 与 acme-companion 容器共享/etc/nginx/vhost.d和/etc/nginx/conf.d两个目录。如果采用三容器部署(nginx、docker-gen、acme-companion 分离),docker-gen 容器也必须共享这些目录(详见 Advanced-usage.md)。

之所以需要共享这两个目录,从 entrypoint.sh 可以看出:只要检测到/app/letsencrypt_user_data存在,启动流程就会强制检查/etc/nginx/vhost.d与/etc/nginx/conf.d是否可写。它们的用途分别对应 functions.sh 中的两个函数:

  • add_standalone_configuration():当域名的server_name未出现在 nginx 现有配置中时,会在/etc/nginx/conf.d/下生成名为standalone-cert-<domain>.conf的独立 server 块,专门应答/.well-known/acme-challenge/路径下的 HTTP-01 验证请求(functions.sh);
  • 若域名已存在于现有 nginx 配置,则改走add_location_configuration(),向/etc/nginx/vhost.d/对应文件追加 challenge location 配置(functions.sh)。

典型的两容器部署如下。

nginx-proxy 容器(需显式声明vhost与conf两个卷):

docker run --detach \ --name nginx-proxy \ --publish 80:80 \ --publish 443:443 \ --volume certs:/etc/nginx/certs \ --volume vhost:/etc/nginx/vhost.d \ --volume conf:/etc/nginx/conf.d \ --volume html:/usr/share/nginx/html \ --volume /var/run/docker.sock:/tmp/docker.sock:ro \ nginxproxy/nginx-proxy

acme-companion 容器(通过--volumes-from继承卷,并将本地配置文件只读挂载到/app/letsencrypt_user_data):

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 \ --volume /path/to/your/config_file:/app/letsencrypt_user_data:ro \ nginxproxy/acme-companion

注意:/etc/nginx/certs与/usr/share/nginx/html两个卷在基本用法中已经需要共享(Basic-usage.md)。如果独立证书全部走 DNS-01 挑战,则无需共享/usr/share/nginx/html。

用户配置文件语法与必选参数

/app/letsencrypt_user_data是一个bash 脚本,由一组普通变量与 bash 数组组成。acme-companion 在每次服务循环中直接source它,因此文件中的语法错误会导致该次循环跳过用户数据(源码中会出现Warning: could not source /app/letsencrypt_user_data的提示,见 letsencrypt_service.sh)。

ACME_STANDALONE_CERTS:证书标识符数组

ACME_STANDALONE_CERTS是一个 bash 数组,存放你所有独立证书的标识符(identifier)。每个元素必须唯一。这些标识符仅存在于容器进程内部,永远不会出现在证书上,也不会被外界看到——它们的作用与 docker-gen 生成的/app/letsencrypt_service_data中ACME_CONTAINERS数组的元素完全对等:在 letsencrypt_service.sh 中,ACME_STANDALONE_CERTS的元素会被逐个当作cid传入update_cert(),与代理容器的容器 ID 走完全相同的证书处理路径。

ACME_<identifier>_HOST:域名数组

对ACME_STANDALONE_CERTS中的每个标识符,都必须有一个对应的ACME_<identifier>_HOST数组,列出该证书要覆盖的域名。<identifier>必须与数组中的标识符逐字一致。数组的第一个域名是基准域名(base domain),决定证书文件的存放目录名。这与代理容器的行为一致:源码update_cert()中hosts_array[0]被用作base_domain与证书目录名(letsencrypt_service.sh)。

向后兼容:LETSENCRYPT_STANDALONE_CERTS

为兼容旧版本,LETSENCRYPT_STANDALONE_CERTS仍可作为ACME_STANDALONE_CERTS的替代。源码中的回退逻辑为:若ACME_STANDALONE_CERTS数组长度为 0,则使用LETSENCRYPT_STANDALONE_CERTS的内容(letsencrypt_service.sh)。其他拥有LETSENCRYPT_旧名的变量,可参考 Environment-variables-reference.md 中的对照表。

单证书单域名最小示例:

ACME_STANDALONE_CERTS=('uniqueidentifier') ACME_uniqueidentifier_HOST=('yourdomain.tld')

多证书多域名示例(每个标识符对应一个 SAN 证书):

ACME_STANDALONE_CERTS=('web' 'app' 'othersite') ACME_web_HOST=('yourdomain.tld' 'www.yourdomain.tld') ACME_app_HOST=('myapp.yourdomain.tld' 'myapp.yourotherdomain.tld' 'service.yourotherdomain.tld') ACME_othersite_HOST=('yetanotherdomain.tld')

使用 DNS-01 验证的示例:下面的配置中,web与app使用全局/默认配置签发证书,而othersite通过独立的 DNS-01 API 配置完成验证:

ACME_STANDALONE_CERTS=('web' 'app' 'othersite') ACME_web_HOST=('yourdomain.tld' 'www.yourdomain.tld') ACME_app_HOST=('myapp.yourdomain.tld' 'myapp.yourotherdomain.tld' 'service.yourotherdomain.tld') ACME_othersite_HOST=('yetanotherdomain.tld') ACME_othersite_CHALLENGE=DNS-01 declare -A ACMESH_othersite_DNS_API_CONFIG=( ['DNS_API']='dns_cf' ['CF_Token']='<CLOUDFLARE_TOKEN>' ['CF_Account_ID']='<CLOUDFLARE_ACCOUNT_ID>' ['CF_Zone_ID']='<CLOUDFLARE_ZONE_ID>' )

注:DNS_API的值是 acme.sh 的 DNS API 名称(如dns_cf对应 Cloudflare);其余键值对依 DNS 服务商而异,可参考 acme.sh 官方文档。acme-companion 固定使用某个版本的 acme.sh,因此文档可能包含当前镜像中尚未提供的服务商。

可选配置参数

单值变量

变量含义与取值
ACME_<identifier>_EMAIL必须是合法邮箱,用于 Let's Encrypt 在自动续期失败时向你发送证书即将过期的警告
ACME_<identifier>_KEYSIZE决定所请求私钥的尺寸。合法值与ACME_KEYSIZE一致:RSA2048/3072/4096/8192,或椭圆曲线ec-256/ec-384/ec-521,默认 4096(详见 Let's-Encrypt-and-ACME.md)
LETSENCRYPT_<identifier>_TEST设为true时为测试证书:不受每周每域名 5 张证书的速率限制约束,但由不受信任的中间 CA 签名(浏览器不信任)

关于LETSENCRYPT_<identifier>_TEST的实现:源码中该变量一旦为真,ACME_CA_URI会被强制覆盖为 Let's Encrypt v2 staging 端点,账户邮箱被清空、配置目录切换为staging,证书目录还会加上_test_前缀(letsencrypt_service.sh)。

DNS-01 相关变量

  • ACME_<identifier>_CHALLENGE:默认HTTP-01;要切换到 DNS-01 ACME 挑战,将其设为DNS-01。在 letsencrypt_service.sh 中,该变量会先回退到全局ACME_CHALLENGE(默认HTTP-01),随后决定--issue使用--webroot还是--dns参数。注意:HTTP-01 挑战不支持通配符证书(*.example.com),源码会在检测到通配符 base domain 时直接报错返回(letsencrypt_service.sh)。
  • ACMESH_<identifier>_DNS_API_CONFIG:默认回退到全局ACMESH_DNS_API_CONFIG(该全局变量由 docker-gen 从 JSON/YAML 字符串解析为关联数组DEFAULT_ACMESH_DNS_API_CONFIG,见 letsencrypt_service_data.tmpl)。如果你希望为某个独立证书指定特定的 DNS-01 验证方法,必须将其定义为bash 关联数组。DNS_API键始终必需;可选DNS_SLEEP键可指定 acme.sh 等待 TXT 记录传播的秒数,会被转换为--dnssleep参数(letsencrypt_service.sh)。

示例:

declare -A ACMESH_alt_DNS_API_CONFIG=( ['DNS_API']='dns_cf' ['CF_Token']='<CLOUDFLARE_TOKEN>' ['CF_Account_ID']='<CLOUDFLARE_ACCOUNT_ID>' ['CF_Zone_ID']='<CLOUDFLARE_ZONE_ID>' )

从源码实现看,ACMESH_<cid>_DNS_API_CONFIG的解析遵循"默认配置 → 单证书配置"的优先级:若单证书关联数组存在且含DNS_API键则使用之,否则回退到全局默认(letsencrypt_service.sh)。该机制与代理容器的ACMESH_DNS_API_CONFIG处理完全一致。

拾取/app/letsencrypt_user_data的变更

acme-companion 并不会主动监听(watch)该文件的变化。变更的生效时机有两种:

  1. 每小时服务循环:acme-companion 的服务循环默认每 3600 秒执行一次(CERTS_UPDATE_INTERVAL,见 letsencrypt_service.sh),每次循环都会重新source用户数据文件;
  2. 手动触发:执行
docker exec your-le-container-name-or-id signal_le_service

signal_le_service实际上是向容器内的letsencrypt_service进程发送USR1信号(signal_le_service.sh),服务循环随即重新执行并拾取最新配置。

代理到非 Docker 容器的场景

如果你的用例是把流量代理到非 Docker 容器的其他服务(例如宿主机上直接运行的进程),请参考nginx-proxy官方文档中关于 proxy-wide 配置的说明。需要注意的是:acme-companion 仓库不为这类代理问题提供支持,代理相关的问题请到 nginx-proxy 项目寻求帮助。

附录:独立证书的完整处理链路

从仓库源码可以完整还原一条独立证书的签发链路,便于排查问题:

  1. 启动校验:入口脚本检测到/app/letsencrypt_user_data存在后,强制校验/etc/nginx/vhost.d与/etc/nginx/conf.d可写(entrypoint.sh);
  2. 加载配置:update_certs()依次source/app/letsencrypt_service_data与/app/letsencrypt_user_data(letsencrypt_service.sh);
  3. 生成挑战应答配置:对每个走 HTTP-01 挑战的独立证书域名,调用add_standalone_configuration()生成standalone-cert-<domain>.conf,或走add_location_configuration()追加 location 配置,然后reload_nginx(letsencrypt_service.sh);
  4. 签发证书:每个标识符作为cid传入update_cert(),与代理容器的处理完全一致——包括账户注册、--issue参数组装(密钥尺寸、挑战类型、OCSP、证书 profile、hooks 等)与符号链接创建(letsencrypt_service.sh);
  5. 清理:签发完成后删除对应standalone-cert-*.conf文件并重载 nginx(letsencrypt_service.sh),cleanup_links()则负责清除不再启用的域名符号链接(letsencrypt_service.sh)。

相关测试可在 test/tests 目录中找到,例如 certs_standalone/run.sh 即为独立证书场景的集成测试。

结语

独立证书功能把 acme-companion 的证书管理能力从"容器环境变量驱动"扩展到了"任意域名驱动":通过一个挂载的 bash 配置文件,你就能复用整套 ACME 签发、续期与符号链接管理机制,同时按标识符粒度单独定制邮箱、密钥尺寸、测试模式与 DNS-01 提供商配置。结合 Environment-variables-reference.md 与 Let's-Encrypt-and-ACME.md 阅读,可以进一步掌握这些参数与代理容器参数之间的对应关系。

  • 云原生
  • 运维

【免费下载链接】acme-companion

Automated ACME SSL certificate generation for nginx-proxy

项目地址:https://gitcode.com/gh_mirrors/ac/acme-companion
点击查看免费下载
上一篇:neovim-flake调试技巧:解决Nix配置中的常见问题
下一篇:TSF协程编程完全解析:从同步代码到异步性能的魔法

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

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

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

立即咨询