在 docker 环境下使用 acme.sh 自动签发 ssl 范域名证书

acme.sh 是一个使用 shell 编写的ACME协议客户端,可以用于自动签发 ssl 证书并且自动更新证书。本文主要介绍在 docker 环境下使用 nginx 反向代理时,如何通过 acme.sh 自动签发和更新 ssl 证书供 nginx 使用。

首先,让我们整理一下在使用云服务器厂商免费的 ssl 证书时,是一个怎么样的步骤:

  1. 在云服务器厂商网页上申请免费的 ssl 证书,在签发 ssl 证书时还需要在云服务器的 dns 上添加记录,签发完成后再删除这条记录。
  2. 将签发完成的ssl证书文件下载下来并且上传到云服务上。
  3. 更新 ssl 证书文件重新加载 nginx。

上述流程上如果要自动化我们需要:

  1. 云服务器厂商提供的修改dns 的api
  2. 可以签发免费证书的 ca 机构
  3. 自动执行上述操作的工具

让我们开始行动:

获取云服务器厂商自动化修改 dns 的api 密钥

可以参考 acme.sh 文档介绍,选择 acme.sh 支持的云服务器厂商 Github

我这里以腾讯云的 dnspod 为例,其他云服务器厂商同理

登录 dnspod 控制台 https://dnspod.cn 生成一个 DNSPod Token,记录 ID 和 Token 后续会使用

Image

选择一个 acme.sh 支持的 CA

还是参考 acme.sh 文档选择一个 ca Github

我这里以 ZeroSSL 为例

  1. 前往 ZeroSSL 官网注册一个账号 https://zerossl.com
  2. 创建完成后再账号页面的 Developer 页面创建一个 ACME客户端的EAB证书,记录 eab-kid 和 eab-hmac-key

Image

在 docker 上使用 acme.sh 自动生成 ssl 证书并拷贝到 nginx 容器中

Github

  1. 通过 docker compose 启动 nginx 和 acme.sh 容器

假设你需要签发证书的域名为 example.com

volumes:
  # 创建一个命名卷用于持久化 nginx 数据
  nginx-data:
    driver: local
  # 创建一个命名卷用于持久化 acme.sh 数据
  acme-sh-data:
    driver: local
 
services:
  # nginx 容器
  nginx:
    image: nginx
    # 为 nginx 容器打上一个标签,让 acme.sh 容器识别出
    labels:
      - sh.acme.autoload.domain=example.com
 
  # acme.sh 容器
  acme.sh:
    image: neilpang/acme.sh
    command: daemon
    volumes:
      # 将需要持久化的数据挂载到 acme-sh-data 上
      - acme-sh-data:/acme.sh
      # 让 acme.sh 容器可以访问外部 docker api
      - /var/run/docker.sock:/var/run/docker.sock
    environment:
      # 识别 ngxin 容器
      - DEPLOY_DOCKER_CONTAINER_LABEL=sh.acme.autoload.domain=example.com
      # 配置 acme.sh 生成的 ssl 证书文件拷贝到 nginx 容器里的位置
      - DEPLOY_DOCKER_CONTAINER_KEY_FILE=/etc/nginx/ssl/example.com/key.pem
      - DEPLOY_DOCKER_CONTAINER_CERT_FILE="/etc/nginx/ssl/example.com/cert.pem"
      - DEPLOY_DOCKER_CONTAINER_CA_FILE="/etc/nginx/ssl/example.com/ca.pem"
      - DEPLOY_DOCKER_CONTAINER_FULLCHAIN_FILE="/etc/nginx/ssl/example.com/full.pem"
      # 当重新签发证书时重新刷新 nginx 配置的命令
      - DEPLOY_DOCKER_CONTAINER_RELOAD_CMD="service nginx force-reload"

启动容器

docker compose up -d
  1. 配置ZeroSSL.com CA api

Github

使用外部账号绑定引导 acme.sh,将 ${ZeroSSL-EAB-KID}${ZeroSSL-EAB-HMAC-KEY}替换为你自己的值

docker exec acme.sh --register-account --server zerossl \
    --eab-kid ${ZeroSSL-EAB-KID}  \
    --eab-hmac-key ${ZeroSSL-EAB-HMAC-KEY}
  1. 签发一个证书
docker exec \
    -e DP_Id=${DNSPod-Id} \
    -e DP_Key=${DNSPod-Token} \
    acme.sh --issue -d example.com -d *.example.com --dns dns_dp
  • ${DNSPod-Id}${DNSPod-Token} 填写在 DNSPod 获取的 id 和 token
  • -d example.com -d *.example.com 生成的是范域名证书,即这个证书可以用于 example.com 的任何子命名(比如 test.example.com),如果只是生成单个域名的证书可以写 -d example.com ,如果需要支持二级域名的范域名可以增加一个 -d *.xxx.example.com

DP_Id, DP_Key 和 dns_dp 三个变量在不同的云服务器厂商下名字不同,请参考 acme.sh 文档填写

  1. 部署证书文件到 nginx 容器里
docker exec acme.sh --deploy -d example.com -d *.example.com --deploy-hook docker

执行完成相关的证书文件已经拷贝到了 nginx 容器里,并且重新加载了 nginx 配置。这个操作完成后 acme.sh 容器每隔 60 天就会重新签发并刷新 nginx 容器,不需要再手动执行。

  • 如果需要手动强制签发,只需要执行:
docker exec acme.sh --renew -d example.com -d *.example.com --force
  • 如果想停止后续的自动签发,只需要执行:
docker exec acme.sh --remove -d example.com -d *.example.com