Skip to content

使用 acme.sh 集中申请和统一分发 SSL 证书

发布于 at 19:15

前言

我之前写了一篇《使用 Caddy 和 acme.sh 实现反向代理》,主要介绍的是单一机器上,使用 acme.sh 申请证书,使用 Caddy 作为反向代理的一套方案。

但当机器多起来,每一台机器都要重新安装 acme.sh,每次申请下来的证书的有效期也不统一,管理起来很麻烦。

所以这篇文章主要讲一下在一台机器上使用 acme.sh 集中申请和统一分发 SSL 证书的方案。

因为本文重点在于 acme.sh,对于 Caddy 只会一笔带过,可以看上面这篇文章来了解。

安装 acme.sh

参照 acme.sh 项目的 Wiki - How to install,里面有一行命令安装:

curl https://get.acme.sh | sh -s [email protected]

但我这里用“高级安装”中的方式:

git clone --depth 1 https://github.com/acmesh-official/acme.sh.git
cd acme.sh
./acme.sh --install \
--home ~/acme \
--config-home ~/acme/config \
--cert-home  ~/acme/certs \
--accountemail  "[email protected]"

配置远程服务器

在本篇文章中,我将会用“本地服务器”指代部署了 acme.sh 用于申请证书的服务器,“远程服务器”指代最终分发证书的目标服务器。

这一部分要注意是本地服务器还是远程服务器,远程服务器是默认用户还是 acme 用户。我在每次切换时都用粗体标注出来了,希望能起到提醒作用。

在分发证书到远程服务器之前,我们需要先配置好远程服务器以便下一步操作。

创建 acme 用户

因为后续分发时,acme.sh 的 Deploy API 限制(支持多个服务器地址,但用户名称并未给出多个的选项),所以统一新建一个用户 acme。

切换到远程服务器

# 添加 acme 用户
sudo useradd -m -g nogroup -s /bin/bash acme
# 把 acme 用户添加到 sudo 组
sudo usermod -aG sudo acme
# 给 acme 用户设置密码
sudo passwd acme

需要注意的是,上面的操作是基于 visudo 中有 %sudo ALL=(ALL:ALL) ALL,即给予 sudo 组的用户执行 sudo 的权限。如果没有的话,acme 用户执行 sudo 时会报错,解决方法就是添加前面这一行。

su - acme

接下来就切换到 acme 用户

mkdir ~/.ssh
sudo chmod 700 ~/.ssh
touch ~/.ssh/authorized_keys
sudo chmod 600 ~/.ssh/authorized_keys

交换 SSH 密钥

切换到本地服务器上,如果没有生成 SSH 密钥,可以使用如下命令:

ssh-keygen -t ed25519 -a 256 -C "[email protected]"
# 可以一路回车,也可以按你需要操作。
# 公钥默认是保存到 ~/.ssh/id_ed25519.pub

然后复制公钥,

cat ~/.ssh/id_ed25519.pub

切换到远程服务器acme 用户

vim ~/.ssh/authorized_keys
# 粘贴本地服务器的公钥

可以在本地服务器尝试 SSH 连接到远程服务器,确认能从本地服务器免密登录到远程服务器的 acme 用户

免密重载 Caddy 服务

更新证书后一般需要重载 Caddy 服务 sudo systemctl reload caddy,涉及 sudo 不好自动执行,所以需要配置免密执行这个命令。

切换到远程服务器

sudo vim /etc/sudoers.d/acme-nopassword
acme ALL=(ALL) NOPASSWD: /bin/systemctl reload caddy
su - acme

切换到 acme 用户

sudo systemctl reload caddy
# 确认是不是不用输入密码就可以执行。

其他

我是打算将证书都保存到远程服务器的 /home/acme/certs 文件夹中。

切换到远程服务器acme 用户

mkdir ~/certs

还要安装 acl 来管理证书文件权限。

sudo apt update
sudo apt install acl

到此,远程服务器就配置完成了,之后的操作均在本地服务器上。

使用 DNS API 申请证书

参照 acme.sh 项目的 Wiki - How to issue a cert,因为打算申请泛域名证书,所以要用 DNS API 来验证所有权。

因为我的域名托管在 CloudFlare,这里就以 CloudFlare 举例,其他 DNS 解析服务商具体操作见 How to use DNS API

创建 API 令牌

打开 CloudFlare,登录账号,右上角头像“我的个人资料”。

左侧“API 令牌”,然后点击“创建令牌”,使用 “API 令牌模板”中的“编辑区域 DNS”的模板。

创建用户 API 令牌

获取账户 ID

因为很可能涉及多个域名,最好是用账户 ID 而不是单一域名区域 ID。账户 ID 就是登录后网址最后那串字符。

申请泛域名证书

export CF_Token="<cloudflare-api-token>" # 替换成你的 API 令牌
export CF_Account_ID="<cloudflare-account-id>" # 替换成你的账户 ID
acme.sh --issue --dns dns_cf -d example.com -d '*.example.com'

因为 acme.sh 会保存 CF_TokenCF_Account_ID 到配置文件中,如果没有更新的话,后面申请可以省略前两行。

使用 Deploy API 分发证书

参照 acme.sh 项目的 Wiki - deployhooks,可以通过 SSH 将证书部署到远程服务器。SSH 这个方法比较通用,但 Wiki 里也有其他特定场景的方法。

# ⚠️ 注意将 admin 换成你的用户名,将 1.1.1.1 换成你的地址。
# export DEPLOY_SSH_CMD='ssh -p 8822'
export DEPLOY_SSH_USER=acme
export DEPLOY_SSH_SERVER="1.1.1.1 1.2.3.4"
# ⚠️ 注意各种路径中的 example.com 换成你自己的域名。
export DEPLOY_SSH_KEYFILE="~/certs/example.com.pem"
export DEPLOY_SSH_CERTFILE="~/certs/example.com.crt"
export DEPLOY_SSH_CAFILE="~/certs/example.com.ca.crt"
export DEPLOY_SSH_FULLCHAIN="~/certs/example.com.fc.crt"
export DEPLOY_SSH_REMOTE_CMD="chmod 600 ~/certs/example.com.pem; \
setfacl -R -m u:caddy:rX ~/certs; \
sudo systemctl reload caddy"
export DEPLOY_SSH_BACKUP=no
# ⚠️ 注意下面命令中的 example.com 换成你自己的域名。
acme.sh --deploy -d example.com -d '*.example.com' --deploy-hook ssh

如果需要修改配置,重新执行一遍就会覆盖原有配置。

安装证书到本地

如果本地服务器也需要证书,使用下面的命令就可以安装到特定位置了。

需要注意的是,因为本地服务器并没有创建 acme 用户,下面的 ~/certs 是在默认用户的家目录中。

acme.sh --install-cert -d example.com -d '*.example.com' \
--key-file ~/certs/example.com.pem \
--cert-file ~/certs/example.com.crt \
--ca-file ~/certs/example.com.ca.crt \
--fullchain-file ~/certs/example.com.fc.crt \
--reloadcmd "chmod 600 ~/certs/example.com.pem;\
setfacl -R -m u:caddy:rX ~/certs;\
sudo systemctl reload caddy"

启动 Caddy

sudo vim /etc/caddy/Caddyfile
www.example.com {
	encode gzip
	tls /home/acme/certs/example.com.fc.crt /home/acme/certs/example.com.pem
	reverse_proxy 127.0.0.1:8080
}
sudo caddy fmt --overwrite /etc/caddy/Caddyfile
sudo systemctl start caddy
sudo systemctl status caddy

配置 ntfy 通知

我今天还写了一篇《使用 ntfy 推送通知》,从安装、配置到使用全流程地带着你入手 ntfy。

因为 acme.sh 的 ntfy 通知脚本比较朴素,连身份验证方式都没给出,我在上面的文章中也讲了如何在 acme.sh 中使用 ntfy 通知,点我查看

最终结果如下:

export NTFY_URL="https://ntfy.example.com"
export NTFY_TOPIC="acmesh?auth=QmFza..."
acme.sh --set-notify \
--notify-hook ntfy \
--notify-level 3 \
--notify-mode 0