手里吃灰的域名越来越多,偶尔还是会拿来测试测试,每次手动配置证书就很麻烦,刚好最近 Let's Encrypt Authority 也由 X3 更新为了 R3 版本,使用了新的 OCSP 地址,解决了污染问题。(此处想说点什么,但还是算了QAQ)因此又用回 acme.sh 自动签发更新证书,官方有很详细的使用文档,这里只做简单记录。

安装

curl  https://get.acme.sh | sh

若后面出现 command not found,则需要手动执行以下命令:

source ~/.bashrc

生成证书

HTTP 认证方式

该方式 acme.sh 会在你的指定的网站根目录下自动生成一个文件,来验证域名所有权,然后自动完成验证并签发证书:

acme.sh  --issue  -d domain.tld -d www.domain.tld  --webroot  /home/wwwroot/domain.tld/

完成后会自动删除验证文件,无需多余操作。

DNS 认证方式(推荐)

这里为了方便,选择通过 DNS 认证方式,这种方式不需要服务器和公网 IP,只需要 DNS 的解析记录即可完成验证,一般主流域名服务商都提供 API 接口,acme.sh 目前支持包括主流的 CloudFlare、DNSPod、Aliyun、Amazon Route53 在内的多达 131 个的域名 API,你这可以在这里查看到详细的支持列表。我的 DNS 服务器一般用的是 Cloudflare,因此这里以 Cloudflare 的 API 为例验证,获取 Cloudflare API,可以自己选择全局 API 还是单域 API,我这里选择了单域 API,获取到 API 后注意保存,然后导入:

export CF_Token="xxxxxxxxxxxxxxxxxxxxxxxx"
export CF_Account_ID="xxxxxxxxxxxxxxxxxxxxxxxx"
export CF_Zone_ID="xxxxxxxxxxxxxxxxxxxxxxxx"

其中 CF_Token 为你获取到的API密钥,CF_Account_ID 和 CF_Zone_ID 可以在你域名概述页面的 API 分栏看到。

签发证书:

acme.sh --issue --dns dns_cf -d domain.tld -d www.domain.tld

若要生成通配符证书,则:

acme.sh --issue --dns dns_cf -d domain.tld -d *.domain.tld

如果要签发 ECC 证书,需要在后面加上 --keylength 选项,下面的示例是签发一张 256 位长度的 EEC 通配符证书:

acme.sh --issue --dns dns_cf -d domain.tld -d *.domain.tld --keylength ec-256

2021 年 6 月 29 日更新

今天准备签发一张证书,结果发现提示错误:

acme.sh is using ZeroSSL as default CA now.
Please update your account with an email address first.

然后去 Github 上项目看了下,发现一篇公告,说是从 8 月 1 日起,默认 CA 将换成 ZeroSSL,不过这不是还没到 8 月 1 日么...虽然 ZeroSSL 以前了解过也不错,同样支持免费签发 90 天期限的证书,包括通配符和 ECC 证书,甚至还支持 IP 证书,不过需要注册账号才行。至于为什么变成了 ZeroSSL,从这篇公告来看,应该是 acme.sh 被 apilayer 收购了。不过我还是习惯了 Let's Encrypt 的方便,想要继续使用 Let's Encrypt 证书,公告里也给出了两种解决方案。

第一种是签发证书时指定 CA:

acme.sh --issue --dns dns_cf -d domain.tld --server letsencrypt

另一种是直接更改默认 CA:

acme.sh --set-default-ca --server letsencrypt

如果设置了默认的 CA,以后就算版本升级也将一直默认使用指定的 CA。


大概 30s 左右就能成功签发证书,证书生成后会将你前面提供的 API 信息自动记录下来,将来在使用的时候就不需要再次指定了,直接生成:

acme.sh --issue -d mydomain2.com --dns dns_cf

安装证书

默认生成的证书都放在安装目录下:~/.acme.sh/,建议使用 --install-cert 命令指定目标位置,将证书文件复制到相应的位置,这里用 Nginx 示例:

acme.sh --install-cert -d domain.tld \
--key-file       /path/to/ssl/private.key  \
--fullchain-file /path/to/ssl/fullchain.pem \
--reloadcmd     "service nginx restart"

如果是安装 ECC 证书:

acme.sh --install-cert -d domain.tld --ecc \
--key-file       /path/to/ssl/private.key  \
--fullchain-file /path/to/ssl/fullchain.pem \
--reloadcmd     "service nginx restart"

这里指定的所有参数都会被自动记录下来,并在将来证书自动更新以后,被再次自动调用,目前证书在 60 天以后会自动更新,无需任何操作。

如果要撤销一个证书,使用:

acme.sh --list
acme.sh --revoke -d domain.tld
acme.sh --revoke -d domain.tld --ecc

如果要删除一个证书,使用:

acme.sh --list
acme.sh --remove -d domain.tld
acme.sh --remove -d domain.tld --ecc

更新 acme.sh

升级 acme.sh 到最新版:

acme.sh --upgrade

开启自动升级:

acme.sh  --upgrade  --auto-upgrade

关闭自动更新:

acme.sh --upgrade  --auto-upgrade  0

关于自动更新证书失败

可能会有多种情况导致更新失败,一是如果时国内服务器自动更新可能会遇到各种玄学网络问题,二是前面提到的 acme.sh 更换了默认的 CA,如果你手动签发一次会发现相关的错误信息,解决办法已在前文提及,而我主要遇到的时另一种情况。

其实以前就发现过自动更新证书失败的情况,但服务器和域名比较多,有的成功有的失败就没太在意。因为我基本上都用的 Cloudflare API 进行签发,最近发现有部分域名 DNS 设置里面出现了几十条用于认证的 TXT 记录(吐槽一下,Cloudflare 不能多选删除,一条一条的删累死了...),于是准备研究一下问题出在哪里。

通过前面大量的 TXT 记录可以推断出 API 是调用成功了的,但却签发失败了,于是直接打开 .acme.sh/account.conf 文件,发现里面记录的 API Token 居然只有一个域名的,然后在 Github 上的一条 issue 中发现了问题所在,acme.sh 默认只会保留最新的域名 Token 信息,如果你在一台服务器上设置了多个域名,那么新设置的域名 Token 就会直接覆盖掉前面的,导致前面的域名更新证书时调用的是新域名的 Token,自然就会认证失败,而且由于失败后多次尝试,也会在最新域名的 DNS 里留下大量的 TXT 记录。

简单来说就是同一个 DNS 服务商通过 API 签发证书,只能保存一个域名的 Token 信息。而解决方法有三种,推荐前两种:

1. 使用不同的 Linux 用户进行证书申请。

2. 签发时指定 home 文件路径,通过添加 --config-home 参数实现:

mkdir /root/.acme2
acme.sh --config-home /root/.acme2 --issue --dns dns_cf -d domain.tld

此时你的 crontab 自动任务也要做出对应的修改,新增一个任务,并且时间最好和默认的 acme.sh 任务错开:

# 默认任务
20 0 * * * "/root/.acme.sh"/acme.sh --cron --home "/root/.acme.sh" > /dev/null
# 新增任务
40 0 * * * "/root/.acme.sh"/acme.sh --cron --home "/root/.acme.sh" --config-home "/root/.acme2" > /dev/null

3. 签发证书时指定配置文件,通过添加 --accountconf 参数实现:

acme.sh --issue --dns dns_cf -d domain.tld --accountconf /root/.acme.sh/account-custom.conf

同样,crontab 自动任务也要做出对应的修改。

一个完整的演示

为了使整个流程更加清晰方便自己操作,这里贴一个自己平时使用 acme.sh 申请证书的完整流程。

  • 申请方式:DNS 认证(使用 Cloudflare API)
  • 申请证书 CA:Let's Encrypt
  • 申请证书类型:RSA + ECC 两个通配符证书
  • * 同一台主机多个域名的情况
# 安装 acme.sh
curl  https://get.acme.sh | sh

# 刷新 shell 变量配置
source ~/.bashrc

# 导入 Cloudflare API Token
export CF_Token="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
export CF_Account_ID="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
export CF_Zone_ID="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

# 设置默认 CA 为 Let's Encrypt
acme.sh --set-default-ca --server letsencrypt

# 签发 RSA 证书
acme.sh --issue --dns dns_cf -d domain.tld -d *.domain.tld

# 安装 RSA 证书到指定路径
acme.sh --install-cert -d domain.tld \
--key-file       /path/to/ssl/domain.tld_private.key  \
--fullchain-file /path/to/ssl/domain.tld_chain.pem \
--reloadcmd     "systemctl restart nginx"

# 签发 ECC 证书,若不需要,跳过即可
acme.sh --issue --dns dns_cf -d domain.tld -d *.domain.tld --keylength ec-256

# 安装 ECC 证书到指定路径
acme.sh --install-cert -d domain.tld --ecc \
--key-file       /path/to/ssl/ecc_domain.tld_private.key  \
--fullchain-file /path/to/ssl/ecc_domain.tld_chain.pem \
--reloadcmd     "systemctl restart nginx"

# 取消环境变量
unset CF_Token
unset CF_Account_ID
unset CF_Zone_ID

# 至此证书安装完毕
# 下面是同一台主机申请第二个域名证书

# 导入另一个 Cloudflare API Token
export CF_Token="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
export CF_Account_ID="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
export CF_Zone_ID="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

# 新建一个文件夹作为 config home,默认会把所有文件存在这里
mkdir /root/.acme2

# 设置默认 CA 为 Let's Encrypt
acme.sh --config-home /root/.acme2 --set-default-ca --server letsencrypt

# 签发 RSA 证书
acme.sh --config-home /root/.acme2 --issue --dns dns_cf -d domain.tld -d *.domain.tld

# 安装 RSA 证书到指定路径
acme.sh --config-home /root/.acme2 \
--install-cert -d domain.tld \
--key-file       /path/to/ssl/domain.tld_private.key  \
--fullchain-file /path/to/ssl/domain.tld_chain.pem \
--reloadcmd     "systemctl restart nginx"

# 签发 ECC 证书,若不需要,跳过即可
acme.sh --config-home /root/.acme2 --issue --dns dns_cf -d domain.tld -d *.domain.tld --keylength ec-256

# 安装 ECC 证书到指定路径
acme.sh --config-home /root/.acme2 \
--install-cert -d domain.tld --ecc \
--key-file       /path/to/ssl/ecc_domain.tld_private.key  \
--fullchain-file /path/to/ssl/ecc_domain.tld_chain.pem \
--reloadcmd     "systemctl restart nginx"

# 手动添加另一个 cron 任务
# crontab -e
50 0 * * * "/root/.acme.sh"/acme.sh --cron --home "/root/.acme.sh"  --config-home "/root/.acme2" > /dev/null