Homelab 域名证书自动化:acme.sh + 腾讯云 / 七牛 CDN 部署流水线

本文最后更新于:2026年11月6日 下午

背景

我的 DNS 用的是腾讯云的 DNSPod。

我的博客其实用到了 3-4 个域名,分布在多个云上:

  • ewhisper.cn 博客站(部署在腾讯云 CDN 上)
  • www.ewhisper.cn 也是指向博客站(部署在腾讯云 CDN 上)
  • pic-cdn.ewhisper.cn 博客站的图床 CDN 域名(部署在七牛云 CDN 上)
  • pic.ewhisper.cn 博客站的图床回源域名(部署在七牛云 CDN 上,好像没用到)

📝Note:

都是历史原因。当初可能是因为方便 / 免费等原因导致了现在这样的架构。
一直规划着重构下,但是没时间搞🤣

这些域名证书的更新,手动续过一轮之后就不想再来第二轮。

一张证书要上传到两个 CDN,每个 CDN 至少两步操作,点完控制台还得等生效。四个域名一轮下来就是十六遍。

实在不想手动点了,索性做成一条流水线:acme.sh 签发,--reloadcmd 钩子自动部署,cron 每 6 小时巡检一次。

📝Notes:
本文是 homelab + 个人博客场景,域名和云资源都在个人账号下,和公司生产环境的多租户、多集群、多审批不是一回事。

整体链路

按签发、部署、验收的顺序拆,一条链路管一张覆盖 4 个域名的多 SAN 证书。

1
2
3
4
5
6
7
cron(每 6h)
└─ acme.sh --cron (含 ARI 检查)
└─ Let's Encrypt DNS-01 via dns_tencent
└─ 续期成功 → --reloadcmd
├─ tccli ssl UploadCertificate → DeployCertificateInstance (腾讯 CDN)
├─ qiniu-deploy.py → /sslcert + /domain/<name>/httpsconf (七牛 CDN)
└─ 日志 → ~/.acme.sh/deploy.log

第一步:复用 tccli 凭据做 DNS-01

acme.sh 的 dns_tencent 插件默认读环境变量,但本机已经装了 tccli,~/.tccli/default.credential 里躺着现成的 SecretId / SecretKey。

直接复用,省掉去 DNSPod 单独申请一套 API Token。

1
2
3
4
5
export Tencent_SecretId="..."
export Tencent_SecretKey="..."
acme.sh --issue --dns dns_tencent \
-d a.ewhisper.cn -d b.ewhisper.cn -d c.ewhisper.cn -d pic.ewhisper.cn \
--keylength 2048

首次签发成功后,dns_tencent 插件用到的 SecretId / SecretKey 会落进 ~/.acme.sh/account.conf,权限 600。这是 DNS API 凭据,TLS 私钥在证书自己的目录下。

第二步:一张多 SAN 证书还是四张独立证书

四个域名,有两种方案:

维度 4 张独立证书 1 张多 SAN 证书
单次续期消耗配额 4 次 1 次
到期时间 可能错开 天然统一
acme.sh --list 4 行 1 行
部署脚本入参 循环 4 次 1 个 cert 路径

考虑到网络 IO 的时间消耗和证书管理的复杂度,最终选择签成一张多 SAN 证书的方案。CA 选 Let’s Encrypt 而不是 ZeroSSL,理由很朴素:LE 不需要邮箱注册,ZeroSSL 要。

📝Notes:
多 SAN 的代价是「一损俱损」,任何一个域名的 DNS-01 验证失败,整张证书都续不上。个人博客站里这些域名共用同一套 DNSPod 解析,风险可以接受。

第三步:腾讯 CDN 部署,重点在 Alias

--reloadcmd 调一个自写脚本,逻辑是先上传再部署。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#!/usr/bin/env bash
set -euo pipefail
CERT_DIR="$1"
ALIAS="ewhisper.cn-acme"

CERT_ID=$(tccli ssl UploadCertificate \
--CertificatePublicKey "$(cat "$CERT_DIR/fullchain.cer")" \
--CertificatePrivateKey "$(cat "$CERT_DIR/ewhisper.cn.key")" \
--Alias "$ALIAS" \
--Repeatable true \
--output json | jq -r '.CertificateId')

tccli ssl DeployCertificateInstance \
--CertificateId "$CERT_ID" \
--ResourceType cdn \
--InstanceIdList "a.ewhisper.cn|on" "b.ewhisper.cn|on"

--Repeatable true 不去重。同一份 PEM 反复上传,每次都返回一个新的 CertificateId,返回体里的 RepeatCertId 始终为空。

「可重复上传」只是允许重复,并不做内容比对。

对策是固定 Alias(ewhisper.cn-acme),按 Alias 周期性批量清理旧证书,而不是在续期钩子里自动删旧证。后者一旦删错,CDN 上正在用的证书就没了。

InstanceIdList 的格式是「域名|计费开关」。off 表示走关闭 CDN 的 HTTPS 功能,所以得写 on。写成裸域名会直接报参数错误。

第四步:七牛的双域名双鉴权

七牛这边麻烦一些,证书管理和域名 HTTPS 配置分属两套 Host、两套鉴权。

用途 Host 鉴权方式 签名要点
证书上传 /sslcert fusion.qiniuapi.com(api.qiniu.com 亦可) QBox 签名原文 path + "\n",body 不参与
域名 HTTPS 配置 /domain/<name>/httpsconf api.qiniu.com Qiniu 必须带 X-Qiniu-Date(UTC,偏差 ≤30s),body 参与签名

混用的结果就是报错:401 或 404,而且报错信息不会告诉你签名方式用错了。

1
2
3
4
5
6
7
8
9
10
11
# qiniu-deploy.py 片段
import requests, time

def qbox_token(ak, sk, path):
sign = hmac_sha1(sk, f"{path}\n") # body 不参与
return f"QBox {ak}:{sign}"

def qiniu_token(ak, sk, host, path, body):
data = f"{host}\n{path}\n{body}" # body 参与
return f"Qiniu {ak}:{hmac_sha1(sk, data)}", time.strftime(
"%Y%m%dT%H%M%SZ", time.gmtime())

这是我碰到的两个错误码:

  • 612:域名不在该 AK 账号下,或者不是 CDN 域名。
  • 404001:路由存在,但域名不归 CDN 管(比如它其实是 Kodo 域名)。

退役一个域名

Kodo 自定义源站域名 pic.ewhisper.cn 要做成自动化,这条路径卡住了。

提了 ticket 七牛工程师在 2026-11-02 确认:“该场景当前不支持通过 API 绑定或替换证书”,官方只提供控制台路径。

选项有两个:改成控制台手工维护,或者直接退役。

退役之前先我先核查了依赖:回源走的是直连空间,PicGo(配合 typora 写博客时自动上传图片到图床) 配置与博客文章 markdown 里 近 5000 处图片引用全部指向 pic-cdn,pic.ewhisper.cn 没有任何消费方。

那就退役:解绑源站域名、删除 DNS CNAME、从脚本的 DOMAINS 和 acme.sh 的 Le_Alt 里移除。

SAN 从 4 个精简到 3 个。

验收

续期成功只说明证书签下来了,部署还得自己验。

先比指纹,命令如下:

1
2
3
IP=$(dig +short A a.ewhisper.cn | head -1)
openssl s_client -connect "$IP:443" -servername a.ewhisper.cn </dev/null 2>/dev/null \
| openssl x509 -noout -fingerprint -sha256

再看续期计划。

1
acme.sh --list

最后核对 key 与 cert 配对,两个公钥的 md5 必须一致。

1
2
openssl x509 -in cert.pem -noout -pubkey | openssl md5
openssl pkey -in key.pem -pubout | openssl md5

当前证书有效期 2026-11-01 至 2027-01-30,下次续期 2026-12-31。

一次 514:配置项开着,服务没开

搭建过程中撞上一次线上 HTTP Status Code: 514,顺手记录排查顺序。

脚本更新证书后,访问博客:ewhisper.cn和 www.ewhisper.cn, 结果都是 514.😱

浏览器打开 F12, 拿到两行判据:HTTP/1.1 514 Frequency Capped 和 X-Cache-Lookup: Return Directly。

接着对照厂商状态码文档逐项证伪:IP 黑白名单、IP 访问限频、证书操作、出口 IP、域名调度。其中「是不是自己刚改坏了」这条,用回滚旧证书、十分钟内恢复原状的方式排除掉了。

真因是两个域名的 CDN HTTPS 服务本身处于关闭状态, 第一次脚本的参数是 off: --InstanceIdList "a.ewhisper.cn|off", off 就直接关闭了 CDN HTTPS 功能。控制台重新启用,立刻恢复 200。

遗留风险

  1. 证书列表会持续增长。腾讯和七牛都不对证书去重,续期一次多一张。需要按固定 Alias 或名称周期性清理。
  2. acme.sh 装在 ~/.acme.sh,依赖登录用户的 crontab。换机、换用户都要整体迁移,包括 account.conf。(所以后面把这套功能塞到 k8s cronjob 里了 😝)
  3. 密钥防泄漏。脚本目录已 git 初始化并推私有仓库,只白名单跟踪不含密钥的脚本和文档;account.conf、*.key、*.cer、deploy.log 和配置快照一律不跟踪,提交前用 git grep 扫一遍。

总结

  • 凭据复用,不新申请 Token:省事,但是凭据权限太大了,万一泄露就完犊子了。
  • 一张多 SAN 证书,不拆四张:节省网络 IO 时间;证书管理简单
  • 固定 Alias 周期性清理,不在钩子里自动删旧证:比如 pic.ewhisper.cn 用不到了,单独删掉即可
  • 无法 API 自动化的域名,核查依赖后直接退役,而不是改成手工维护: 能省事就省事,能自动化就自动化

以上。

📚️参考文档


Homelab 域名证书自动化:acme.sh + 腾讯云 / 七牛 CDN 部署流水线
https://ewhisper.cn/posts/48136/
作者
东风微鸣
发布于
2026年11月6日
许可协议