acme.sh SSL 证书续签:从手动 DNS 迁移到 Cloudflare 全自动续签
#SSL #acme.sh #Cloudflare #Nginx
背景
博客使用 acme.sh + Let’s Encrypt 签发 ECC 证书,原方案依赖阿里云 DNS API(dns_ali)自动验证。DNS 迁移到 Cloudflare 后需同步更新续签方式,同时记录中途遇到的一次手动续签失败的排查过程。
一、故障:续签时 Finalize 报错
错误现象
1 | [ERROR] Le_OrderFinalize |
原因
libcurl 错误码 3 为 CURLE_URL_MALFORMAT,即 acme.sh 缓存的 ACME 订单状态中 Le_OrderFinalize URL 损坏,导致签名阶段失败。
解决
删除缓存的域名配置,强制重新发起订单:
1 | rm -rf ~/.acme.sh/<domain>_ecc/ |
在 DNS 控制台添加提示的 _acme-challenge TXT 记录,等待解析生效后再执行:
1 | ~/.acme.sh/acme.sh --renew -d "<domain>" \ |
二、证书安装到 Nginx
本站多个端口(443 / 18063 / 19090 / 22017 / 4455 / 8900)共用同一份证书,统一放在:
1 | /etc/nginx/ssl/port_18063/port_18063.pem # fullchain |
安装命令:
1 | ~/.acme.sh/acme.sh --install-cert -d "<domain>" --ecc \ |
验证
1 | # TLS 层验证(最可靠) |
注意:IDN 国际化域名(中文域名)在部分字体下 punycode 的
0(数字零)会被渲染为O(大写字母),9被渲染为g,造成视觉上的"名称不匹配",实为字体问题,subjectAltName matched才是判断依据。
三、迁移到 Cloudflare 自动续签
前置条件
-
域名 NS 已切换到 Cloudflare
-
在 CF Dashboard → My Profile → API Tokens 创建 Token,权限:
- Zone - DNS - Edit,仅限目标域名 Zone
持久化 Token
1 | # 写入 account.conf,acme.sh --cron 自动读取,无需每次 export |
正式签发(绑定 dns_cf)
1 | export CF_Token="<your_cf_api_token>" |
注意:IDN 中文域名需使用 punycode 形式传入,
dns_cf不支持直接传中文域名。
安装证书并注册 cron
1 | /root/.acme.sh/acme.sh --install-cert -d "<domain>" --ecc \ |
四、自动续签原理
1 | 每天 00:00 cron 触发 acme.sh --cron |
验证续签配置完整性
1 | # 确认 dns_cf 已绑定 |
五、坑:sudo 下 $HOME 路径错误
sudo bash ssl_setup.sh 时,$HOME 解析为调用用户的家目录(如 /home/username)而非 /root,导致:
-
acme.sh找不到 -
account.conf写入错误位置 -
cron 命令路径错误
解决:脚本中所有 acme.sh 相关路径硬编码为 /root/.acme.sh/,不使用 $HOME 或 ~。
六、前置检查脚本要点
部署前用 ssl_preflight.sh 验证:
-
CF Token 有效性(调用 CF API 查询 Zone)
-
acme.sh 存在于
/root/.acme.sh/ -
证书目录写权限
-
Nginx 配置语法
-
当前证书到期时间
-
Staging 签发测试:用 Let’s Encrypt 测试环境走完完整流程,颁发者应为
(STAGING) ...,不消耗速率限制 -
清理 Staging 残留,恢复生产证书状态
