- 云原生
- 运维
【免费下载链接】acme-companion
Automated ACME SSL certificate generation for nginx-proxy
导读
本文介绍 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-proxyacme-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)该文件的变化。变更的生效时机有两种:
- 每小时服务循环:acme-companion 的服务循环默认每 3600 秒执行一次(
CERTS_UPDATE_INTERVAL,见 letsencrypt_service.sh),每次循环都会重新source用户数据文件; - 手动触发:执行
docker exec your-le-container-name-or-id signal_le_servicesignal_le_service实际上是向容器内的letsencrypt_service进程发送USR1信号(signal_le_service.sh),服务循环随即重新执行并拾取最新配置。
代理到非 Docker 容器的场景
如果你的用例是把流量代理到非 Docker 容器的其他服务(例如宿主机上直接运行的进程),请参考nginx-proxy官方文档中关于 proxy-wide 配置的说明。需要注意的是:acme-companion 仓库不为这类代理问题提供支持,代理相关的问题请到 nginx-proxy 项目寻求帮助。
附录:独立证书的完整处理链路
从仓库源码可以完整还原一条独立证书的签发链路,便于排查问题:
- 启动校验:入口脚本检测到
/app/letsencrypt_user_data存在后,强制校验/etc/nginx/vhost.d与/etc/nginx/conf.d可写(entrypoint.sh); - 加载配置:
update_certs()依次source/app/letsencrypt_service_data与/app/letsencrypt_user_data(letsencrypt_service.sh); - 生成挑战应答配置:对每个走 HTTP-01 挑战的独立证书域名,调用
add_standalone_configuration()生成standalone-cert-<domain>.conf,或走add_location_configuration()追加 location 配置,然后reload_nginx(letsencrypt_service.sh); - 签发证书:每个标识符作为
cid传入update_cert(),与代理容器的处理完全一致——包括账户注册、--issue参数组装(密钥尺寸、挑战类型、OCSP、证书 profile、hooks 等)与符号链接创建(letsencrypt_service.sh); - 清理:签发完成后删除对应
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
相关推荐
acme-companion 实战指南:为 nginx-proxy 自动化签发与续期 ACME SSL 证书
acme companion 实战指南:为 nginx proxy 自动化签发与续期 ACME SSL 证书 本文围绕 nginx proxy 生态中的轻量级伴
云原生运维认证与授权完全指南:从 Session、JWT 到 OAuth 2.0 的后端身份安全实战
认证与授权完全指南:从 Session、JWT 到 OAuth 2.0 的后端身份安全实战 导读 :认证(Authentication)与授权(Authoriz
云原生运维使用 lego 通过 Constellix DNS 提供商签发 ACME 证书:DNS-01 挑战完整配置指南
使用 lego 通过 Constellix DNS 提供商签发 ACME 证书:DNS 01 挑战完整配置指南 本文以 lego(Let's Encrypt/A
网络安全密码学
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考