使用acme.sh自动获取并续签SSL证书

前言

👋 好久不见,前段时间看到证书快过期了却没续上,一看邮箱,OHTTPS的免费次数用完了

OHTTPS网页版的证书管理、签发、续签工具,新用户有一定免费余额
如果只申请一个证书并开自动续期的话,大概能用一年多,感兴趣可以看看

懒得换号注册,也没钱充值,于是去翻Github,找到了800年前Star的这个项目
简单强大且易于使用,能自动签发、部署、续签通配符证书,支援几乎所有常见平台,可Docker部署,支持IPv6,隆重介绍

安装

安装十分简单,以Ubuntu 系统为例,可以直接使用官方提供的命令
两条命令任选其一,注意将my@example.com替换为自己的邮箱,否则可能导致证书签发失败

1
curl https://get.acme.sh | sh -s email=my@example.com
1
wget -O -  https://get.acme.sh | sh -s email=my@example.com

验证域名

与其他签发证书的工具一样,acme.sh 也需要验证域名的所有权,可以使用 HTTP验证 DNS验证 这两种验证方式
大多数情况下 DNS验证 方式会快捷一些,只要你的域名在海内外170余家DNS提供商解析,就可以体验到这一劳永逸的快捷
国内如阿里云、腾讯DNSPod、华为云,海外如CloudFlare等都能使用,覆盖面还是很广的

本篇教程以腾讯云下属的DNSPod为例进行演示,其他 DNS API 的清单及方法指南见下

获取 DNSPod Token

登录 DNSPod 控制台,转到获取API密钥界面

点击创建密钥,扫码完成身份验证,然后随便为密钥起个名称,例如acme.sh
API密钥

记录下显示的密钥,ID即为DP_Id,Token即为DP_Key,后续会用到
API密钥2

设置变量

回到服务器控制台,替换以下为刚刚的数据,然后执行

1
2
export DP_Id=<id>
export DP_Key=<key>

签发证书

现在我们终于可以签发心心念念的通配符证书了,在终端执行下面命令,将两个域名都换成自己的,通配符域名格式为*.xx.xx
DP_Id 和 DP_Key 两个变量(其他API同理)将保存在 ~/.acme.sh/account.conf 中,并在下次需要时自动获取,无需重新设置变量

1
acme.sh --issue --dns dns_dp -d example.com -d *.example.com

复制并部署证书

证书签发完成以后,我们还需要把证书复制给对应的服务去使用
官方提示:必须使用下面演示的 –install-cert 命令来把证书复制到目标文件夹,请勿直接使用 ~/.acme.sh/ 目录下的证书文件,这里面的文件都是内部使用,而且目录结构将来可能会变化

默认情况下,证书每 60 天更新一次;证书续期后,下面指定的 reloadcmd 将会被执行,所以更改 reloadcmd 是我们实现各种部署功能的关键
官方说明给出了部署至 Apache 和 Nginx 的示例,而我会分享部署至宝塔面板站点和多吉云等 CDN 的方法

官方提示:reloadcmd 非常重要,证书会自动申请续签,但是如果没有正确的 reloadcmd 命令,证书可能无法被重新应用到 Apache 或者 Nginx,因为配置没有被重载
目前修改 reloadcmd 没有专门的命令,可以通过重新安装证书(即重新执行acme.sh --install-cert命令)来实现修改 reloadcmd 的目的

Apache部署示例
1
2
3
4
5
acme.sh --install-cert -d example.com \
--cert-file /path/to/certfile/in/apache/cert.pem \
--key-file /path/to/keyfile/in/apache/key.pem \
--fullchain-file /path/to/fullchain/certfile/apache/fullchain.pem \
--reloadcmd "service apache2 force-reload"
Nginx部署示例
1
2
3
4
acme.sh --install-cert -d example.com \
--key-file /path/to/keyfile/in/nginx/key.pem \
--fullchain-file /path/to/fullchain/nginx/cert.pem \
--reloadcmd "service nginx reload"

Nginx 的配置项 ssl_certificate 需要使用 /etc/nginx/ssl/fullchain.cer,而非 /etc/nginx/ssl/<domain>.cer,否则 SSL Labs 的测试会报证书链问题(Chain issues Incomplete)。

部署至 宝塔面板-网站项目

部署至宝塔面板的网站项目本质上就是将证书复制至指定目录
根据网上信息和我的实际情况看,宝塔网站的证书在 /www/server/panel/vhost/cert/ 下,该目录下的各网址文件夹即为存放证书的目录

接下来的事儿就简单多了,我们只要将证书导出并复制到符合域名的网站文件夹
我将需求丢给DeepSeek并加以修改,下面是我自用的一份示例脚本,如果你要使用,请阅读顶上的注意事项栏目

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#!/bin/bash
# 注意事项:
# 1. 请修改 证书存放目录 和 主域名 变量,证书存放目录与--install-cert命令中的存放文件夹一致,主域名和申请证书的域名一致
# 2. 以下面示例数据为例,脚本会遍历/www/server/panel/vhost/cert目录下所有以example.com结尾的文件夹(包括其本身),并将/path/to/keyfile目录下的fullchain.pem、privkey.pem复制到此文件夹
# 3. 简而言之,指定主域名后,脚本会自动将证书复制进主域名本身和其子域名的文件夹内实现宝塔证书自动更新
# 4. 不要更改证书fullchain.pem、密钥privkey.pem的文件名,否则宝塔无法识别

# 定义证书存放目录(改!!)
CERT_DIR="/path/to/keyfile"

# 定义主域名(改!!)
MAIN_DOMAIN="example.com"

# 定义宝塔网站根目录
BT_WEB_ROOT="/www/server/panel/vhost/cert"

# 自动查找主域名及其子域名的目录
DOMAINS=("$MAIN_DOMAIN")
while IFS= read -r -d '' dir; do
domain=$(basename "$dir")
if [[ "$domain" == *".$MAIN_DOMAIN" ]]; then
DOMAINS+=("$domain")
fi
done < <(find "$BT_WEB_ROOT" -maxdepth 1 -type d -name "*.$MAIN_DOMAIN" -print0)

# 初始化成功更新的域名列表
SUCCESS_DOMAINS=()

# 遍历每个域名
for domain in "${DOMAINS[@]}"; do
# 定义宝塔网站的证书存放路径
BT_CERT_DIR="${BT_WEB_ROOT}/${domain}"

# 创建宝塔网站的证书目录(如果不存在)
mkdir -p "${BT_CERT_DIR}"

# 复制证书文件到宝塔网站的证书目录
cp "${CERT_DIR}/fullchain.pem" "${BT_CERT_DIR}/fullchain.pem"
cp "${CERT_DIR}/privkey.pem" "${BT_CERT_DIR}/privkey.pem"

# 检查复制操作是否成功
if [ $? -eq 0 ]; then
SUCCESS_DOMAINS+=("${domain}")
echo "证书更新成功: ${domain}"
else
echo "证书更新失败: ${domain}"
fi
done

# 重载宝塔网站配置,使证书生效(只需要执行一次)
service nginx force-reload

# 输出更新完成信息
echo "宝塔面板网站证书更新完成!"
echo "成功更新的域名:${SUCCESS_DOMAINS[*]}"

将脚本保存为 .sh 文件,别忘记 chmod +x bt.sh 赋予执行权限
执行下面的命令(记得修改文件夹和域名为自己的)以实现自动部署至宝塔网站

1
2
3
4
acme.sh --install-cert -d example.com \
--key-file /path/to/keyfile/privkey.pem \
--fullchain-file /path/to/fullchain/fullchain.pem \
--reloadcmd "/path/to/autobash/bt.sh"
部署至 多吉云和其他CDN

已经备案的网站大多会使用CDN服务,此次教程以融合CDN 多吉云为例说明方法
使用其他CDN厂商的小伙伴可以翻出自己厂商的 CDN API 说明文档,丢给AI根据文档和需求(可参考下面)写适合自己的脚本即可

插播一条小广告: 多吉云的CDN服务有每月20GB的免费额度,融合了阿里、腾讯、华为、网宿等一线 CDN 厂商的节点,质量可靠,稳定易用。感兴趣的小伙伴可以点链接看看 (有我的REF,感谢支持)

说回正题,要使用多吉云的API,你需要先获取AccessKeySecretKey

来看脚本,由于有绑定证书到域名这一环节,需要手动指定域名列表,具体请看注意事项

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
#!/bin/bash
# 注意事项:
# 1. 请修改 AccessKey 和 SecretKey 变量并指定证书路径和域名列表,证书存放目录与--install-cert命令中的存放文件夹一致
# 2. 证书备注名默认为当前日期+90天(即证书到期日),格式例如2025-07-07,可自行修改
# 3. 域名列表内的域名需要与多吉云CDN控制台中的一致,指定多少域名证书就会绑定多少域名

# 多吉云 AccessKey 和 SecretKey
ACCESS_KEY=""
SECRET_KEY=""

# 证书路径
FULLCHAIN_PATH="/path/to/fullchain/fullchain.pem"
PRIVKEY_PATH="/path/to/keyfile/privkey.pem"

# 证书备注名
EXPIRED_DATE=$(date -d "+90 day" +%Y-%m-%d)
NOTE="$EXPIRED_DATE"

# 需要绑定的域名列表
DOMAINS=("example1.com" "example2.com" "example3.com")

# 生成AccessToken
function generateAccessToken() {
local apiPath="$1"
local body="$2"
local signStr=$(echo -e "${apiPath}\n${body}")
local sign=$(echo -n "$signStr" | openssl dgst -sha1 -hmac "$SECRET_KEY" | awk '{print $NF}')
local accessToken="$ACCESS_KEY:$sign"

echo "$accessToken"
}

# 上传证书到多吉云
function uploadCert() {
local note="$1"
local certFile="$2"
local privateKeyFile="$3"
local certContent=$(<"$certFile")
local privateKeyContent=$(<"$privateKeyFile")
local encodedCert=$(echo "$certContent" | jq -sRr @uri)
local encodedPrivateKey=$(echo "$privateKeyContent" | jq -sRr @uri)
local body="note=$note&cert=$encodedCert&private=$encodedPrivateKey"
local accessToken=$(generateAccessToken "/cdn/cert/upload.json" "$body")
local response=$(curl -s -X POST "https://api.dogecloud.com/cdn/cert/upload.json" \
-H "Authorization: TOKEN $accessToken" \
-H "Content-Type: application/x-www-form-urlencoded" \
--data "$body")

local code=$(echo "$response" | jq -r '.code')

if [ "$code" -eq 200 ]; then
echo "证书上传成功!"
local certId=$(echo "$response" | jq -r '.data.id')
echo "证书ID:$certId"
bindCert "$certId"
else
local errMsg=$(echo "$response" | jq -r '.msg')
echo "证书上传失败,错误代码:$code,错误信息:$errMsg"
fi
}

# 绑定证书到域名
function bindCert() {
local certId="$1"
local responses=()

for domain in "${DOMAINS[@]}"; do
(
local body="id=$certId&domain=$domain"
local accessToken=$(generateAccessToken "/cdn/cert/bind.json" "$body")
local response=$(curl -s -X POST "https://api.dogecloud.com/cdn/cert/bind.json" \
-H "Authorization: TOKEN $accessToken" \
-H "Content-Type: application/x-www-form-urlencoded" \
--data "$body")
local code=$(echo "$response" | jq -r '.code')

if [ "$code" -eq 200 ]; then
echo "证书已成功绑定到 $domain"
else
local errMsg=$(echo "$response" | jq -r '.msg')
echo "绑定证书到 $domain 失败,错误代码:$code,错误信息:$errMsg"
fi
) &
done

wait
}

uploadCert "$NOTE" "$FULLCHAIN_PATH" "$PRIVKEY_PATH"

将脚本保存为 .sh 文件,别忘记 chmod +x bt.sh 赋予执行权限
执行下面的命令(记得修改文件夹和域名为自己的)以实现自动部署至宝塔网站

1
2
3
4
acme.sh --install-cert -d example.com \
--key-file /path/to/keyfile/privkey.pem \
--fullchain-file /path/to/fullchain/fullchain.pem \
--reloadcmd "/path/to/autobash/dogecloud.sh"

设置通知

acme.sh 可调用许多通知API来通知你证书续期的情况,你可以使用--set-notify命令设置通知
其支持的通知API中常见的有:邮箱、飞书、钉钉、QQ通知(调用CoolQ估计不太能用)、Discord、TG等

具体名单及其使用方法在官方教程写得很详细,在此不再赘述,善用翻译即可

强制续签证书

证书每60天会自动更新,正常情况下无需操作
如果需要测试部署脚本或出于其他原因,你可以在--renew命令后加--force参数实现强制续签证书,示例如下

1
acme.sh --renew -d example.com --force

更新acme.sh

acme.sh 仍在不断开发和更新,开发者强烈建议保持并使用最新的版本
升级 acme.sh 到最新版:acme.sh --upgrade

以下是自动更新的开启/关闭方法

  • 开启 acme.sh --upgrade --auto-upgrade
  • 关闭 acme.sh --upgrade --auto-upgrade 0

结语

本篇教程到这里接近尾声了,acme.sh作为开源的证书管理方案,有极高的可玩性和自由度
因此教程无法面面俱到,未尽之处可访问acme.sh了解更多信息 (官方域名就长这样,跳转至Github)

用了半年下来体验还是不错的,没出什么问题,一直都能稳定地续签、部署和推送通知
有需求的小伙伴可以尝试,我强烈推荐哦
OK 我们下次再见! 放暑假咯,假期更新频率终于能提升点了……吧?