手里吃灰的域名越来越多,偶尔还是会拿来测试测试,每次手动配置证书就很麻烦,刚好最近 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 mydomain.com -d www.mydomain.com  --webroot  /home/wwwroot/mydomain.com/

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

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 mydomain.com -d www.mydomain.com

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

acme.sh --issue --dns dns_cf -d mydomain.com -d *.mydomain.com

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

acme.sh --issue --dns dns_cf -d mydomain.com -d *.mydomain.com --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 mydomain.com --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 mydomain.com \
--key-file       /path/to/ssl/private.key  \
--fullchain-file /path/to/ssl/fullchain.pem \
--reloadcmd     "service nginx restart"

如果是安装 ECC 证书:

acme.sh --install-cert -d mydomain.com --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 mydomain.com

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

acme.sh --list
acme.sh --remove -d mydomain.com

更新 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. 签发证书时指定配置文件,通过添加 --accountconf 参数实现(推荐):

acme.sh --issue --dns dns_cf -d mydomain.com --accountconf /root/.acme.sh/account-custom.conf

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

# 默认任务
20 0 * * * "/root/.acme.sh"/acme.sh --cron --home "/root/.acme.sh" > /dev/null
# 指定配置
30 0 * * * "/root/.acme.sh"/acme.sh --cron --home "/root/.acme.sh" --accountconf "/root/.acme.sh/account-custom.conf" > /dev/null

此时需要额外注意的是,如果你前面修改了 acme.sh 默认的 CA,那么指定配置文件也需要切换:

acme.sh --set-default-ca --server letsencrypt --accountconf /root/.acme.sh/account-custom.conf

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

acme.sh --issue --dns dns_cf -d mydomain.com --config-home /root/acmeconfig/ 

同样,Crontab 自动任务也要做出对应的修改,新增一个任务:

59 0 * * * "/root/.acme.sh"/acme.sh --cron --home "/root/.acme.sh" --config-home "/root/acmeconfig/" > /dev/null