mongona

mongona
-- --
正在获取天气

一次把阿里云个人测试证书自动申请、DNS 验证和 Nginx 替换跑通

这篇文章记录的是一次真实的证书自动化改造。目标很直接:阿里云个人测试证书有效期只有 90 天,不想每三个月手工点控制台、复制 DNS 记录、下载证书、替换 Nginx。最后做成了一个定时脚本加一个轻量 Cert Admin 管理面板:证书没过期就跳过,过期才自动申请、验证、下载、替换并 reload Nginx。

本文只记录技术流程和关键参数。AccessKey、AccessSecret、后台密码、联系人手机号邮箱等敏感信息都不会出现在文章里,脚本日志里也做了脱敏。

最终效果

  • 证书目标可配置:例如 www.bytealien.comadmin.bytealien.com
  • 脚本先检查本地证书是否已经过期;未过期直接跳过,不会下单,不会申请。
  • 证书过期后,自动查询阿里云个人测试证书实例;没有待申请实例时,按 0 元参数购买一张。
  • 自动读取阿里云联系人,不把姓名、手机号写入本地 env。
  • 自动提交证书申请,读取 DNS 验证记录,写入 AliDNS TXT/CNAME。
  • 签发后自动下载证书和私钥,校验证书与私钥匹配,备份旧文件,替换到 Nginx 使用的路径。
  • 替换后执行 nginx -t,通过后 reload Nginx。
  • Cert Admin 只监听 127.0.0.1:5055,通过 SSH 隧道访问,使用 supervisor 管理。

为什么不能只用旧接口

一开始最容易想到的是阿里云 SSL 证书服务里的几个老接口:CreateCertificateRequestCreateCertificateWithCsrRequestCreateCertificateForPackageRequest。文档里确实能看到 digicert-free-1-free,表示 3 个月个人测试证书免费版。

但实际跑下来,旧接口路径容易卡在资源包额度上。CreateCertificateRequest 会要求联系人信息;补齐联系人之后又可能返回 InsufficientQuotaCreateCertificateForPackageRequest 同样依赖可用资源包。这和现在控制台“个人测试证书(原免费证书)”的实例式流程不是一回事。

真正跑通的是新实例流程:先有一个 inactive 的个人测试证书实例,再对这个实例 UpdateInstanceApplyCertificate、等待签发,最后用证书 ID 下载证书。

阿里云接口链路

当前自动化脚本使用的主链路如下:

  • ListContact:查询证书联系人,拿 ContactId
  • ListInstances:查有没有 Status=inactive 的个人测试证书实例。
  • GetSubscriptionPrice:下单前确认个人测试证书实例价格为 0。
  • CreateInstance:没有待申请实例时,自动购买一个 0 元个人测试证书实例。
  • UpdateInstance:把域名、联系人、CSR 生成方式、验证方式写到实例上。
  • ApplyCertificate:提交证书申请。
  • GetTaskAttribute:确认提交任务是否成功。
  • GetInstanceDetail:轮询状态,读取 DNS 验证记录和最终 CertificateId
  • GetUserCertificateDetail:下载证书和私钥。

0 元个人测试证书实例的 BSS 参数

关键坑在这里。不要用旧的 productCode:digicert-free-1-free,service_num,time 那组参数。那组参数可能创建出控制台看不到、状态一直 Creating 的实例。这次最终验证成功的是下面这组“个人测试证书实例”参数。

GetSubscriptionPrice:
  ProductCode: cas
  ProductType: cas_dv_public_cn
  SubscriptionType: Subscription
  OrderType: NewOrder
  ServicePeriodUnit: Month
  ServicePeriodQuantity: 3
  Quantity: 1
  ModuleList.1.ModuleCode: certdomain
  ModuleList.1.Config: certdomain:dv
  ModuleList.2.ModuleCode: fullDomainCount
  ModuleList.2.Config: fullDomainCount:1
  ModuleList.3.ModuleCode: fullSpec
  ModuleList.3.Config: fullSpec:ss.dv.t
  ModuleList.4.ModuleCode: merge
  ModuleList.4.Config: merge:0
  ModuleList.5.ModuleCode: product
  ModuleList.5.Config: product:testCert_product
CreateInstance:
  ProductCode: cas
  ProductType: cas_dv_public_cn
  SubscriptionType: Subscription
  Period: 3
  RenewalStatus: ManualRenewal
  Parameter.1.Code: certdomain
  Parameter.1.Value: dv
  Parameter.2.Code: fullDomainCount
  Parameter.2.Value: 1
  Parameter.3.Code: fullSpec
  Parameter.3.Value: ss.dv.t
  Parameter.4.Code: merge
  Parameter.4.Value: 0
  Parameter.5.Code: product
  Parameter.5.Value: testCert_product

UpdateInstance 参数

拿到 inactive 实例后,给实例补全申请信息。CSR 使用阿里云在线生成,避免本地私钥和阿里云签发结果不一致。

UpdateInstance:
  InstanceId: cas_dv-cn-xxxx
  CertificateName: cert-www-example-com
  Domain: www.example.com
  KeyAlgorithm: RSA_2048
  AutoReissue: disable
  ContactIdList.1: 15230
  GenerateCsrMethod: online
  ValidationMethod: DNS
  City: Beijing
  Province: Beijing
  CountryCode: CN

ContactIdList.1 来自 ListContact。这里不需要在 env 文件里保存姓名和手机号,运行时查询即可。日志只输出脱敏后的联系人摘要。

DNS 验证

提交 ApplyCertificate 之后,脚本轮询 GetInstanceDetail。当返回 DomainValidationList 时,根据 ValidationType 自动选择 TXT 或 CNAME。

DomainValidationList:
  Domain: www.example.com
  RootDomain: example.com
  ValidationType: TXT
  ValidationKey: _dnsauth
  ValidationValue: 20260630xxxxxxxx

脚本会把 ValidationKey + RootDomain 组合成实际记录名,调用 AliDNS 的 DescribeDomainRecordsAddDomainRecordUpdateDomainRecord。如果记录已经存在且值一致,就直接复用;如果记录存在但值不同,就更新。

证书下载和替换

实例进入 Status=normalCertificateStatus=issued 后,脚本用 CertificateId 调用 GetUserCertificateDetail 下载证书和私钥。

落盘前做两件事:

  • openssl x509 -noout -modulusopenssl rsa -noout -modulus 校验证书私钥是否匹配。
  • 替换目标文件前自动备份旧证书和旧私钥,备份文件带时间戳。
openssl x509 -in /path/to/fullchain.pem -noout -subject -issuer -dates -ext subjectAltName
openssl x509 -noout -modulus -in /path/to/fullchain.pem
openssl rsa  -noout -modulus -in /path/to/private.key
nginx -t
systemctl reload nginx

这次实际替换后的证书由 DigiCert 签发,两个域名的到期时间都是 2026-09-28 23:59:59 GMT

定时任务策略

个人测试证书有效期 90 天。因为需求是“过期了才申请替换”,所以脚本默认检查阈值是 0 秒:没过期就跳过。cron 可以跑得频繁一点,真正的保护逻辑在脚本里。

SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

25 */6 * * * root /usr/local/sbin/renew-aliyun-free-cert.py

这样每 6 小时检查一次,但不会提前消耗免费额度。如果确实要提前重签,可以手动加 --force --domain example.com

Cert Admin 管理面板

为了不每次 SSH 上去改 JSON 或看日志,又做了一个轻量 Cert Admin。它不是暴露公网的后台,只监听 127.0.0.1:5055,通过 SSH 隧道访问。

ssh -L 5055:127.0.0.1:5055 root@your-jump-host
open http://127.0.0.1:5055

面板目前支持:

  • 查看证书目标、证书状态、过期时间、证书路径。
  • 新增、编辑、启用、停用、删除证书目标。
  • 保存自动化开关,例如实例流、自动 0 元购买、购买数量、DNS 根域。
  • 全量只检查、全量续期。
  • 单域名只检查。
  • 单域名立即重签并替换;这个操作会消耗一张个人测试证书额度,所以前端有确认框。
  • 查看运行日志和证书申请日志。
  • 可选生成 Nginx 托管配置,生成前后都会执行 nginx -t

Cert Admin 用 supervisor 管理,不用 systemd 管这个服务。这样和原先服务器习惯一致,服务异常也能自动拉起。

[program:cert-admin]
command=/usr/bin/python3 /opt/cert-admin/server.py
directory=/opt/cert-admin
autostart=true
autorestart=true
stdout_logfile=/var/log/cert-admin.log
stderr_logfile=/var/log/cert-admin.err.log

核心配置

续期脚本的关键开关如下。生产环境里 AK/SK 单独放在 env 文件,日志里必须脱敏。

ALIYUN_USE_INSTANCE_FLOW=1
ALIYUN_AUTO_BUY_FREE_CERT=1
ALIYUN_FREE_CERT_BUY_QUANTITY=1
ALIYUN_USE_PACKAGE_FLOW=0
ALIYUN_USE_LOCAL_CSR=0
ALI_DNS_ZONES=bytealien.com,mongona.com,luxefuture.com,pyfuture.com

ALIYUN_FREE_CERT_BUY_QUANTITY=1 是故意的:多个域名同时过期时,脚本逐个域名购买、逐个申请,不一次囤多张。

排错记录

1. 证书实例控制台看不到

如果 BSS 下单参数走错,可能会出现订单成功但 SSL 控制台“个人测试证书”页面看不到,BSS 实例状态还停在 Creating。解决方式是改用 cas_dv_public_cn + ss.dv.t + testCert_product 这组参数。

2. CreateCertificateRequest 返回 InsufficientQuota

这说明当前接口链路在消耗旧资源包额度。当前控制台的免费证书更像“测试证书实例”流程,所以应切到 ListInstances / UpdateInstance / ApplyCertificate

3. 联系人不要写死

联系人信息用 ListContact 查询。脚本只需要 ContactId,不需要把手机号、邮箱、姓名明文写到 env。

4. 先验证再 reload

证书替换后必须先 nginx -t。如果证书路径、私钥权限或 server block 写错,直接 reload 会影响线上服务。

这套方案的边界

  • 个人测试证书是 90 天,不适合作为有 SLA 要求的商业证书方案。
  • 免费 DV 证书只按单域名处理;不要把通配符或多域名场景混到同一个目标里。
  • 自动下单前必须查价格,确认是 0 元再创建实例。
  • Cert Admin 不应直接暴露公网;保持 127.0.0.1 监听,通过 SSH 隧道访问。
  • 日志可以记录请求参数,但必须屏蔽 AccessKey、Secret、Token、手机号、邮箱和私钥内容。

小结

这次真正重要的不是“写了一个续期脚本”,而是把阿里云个人测试证书现在实际可用的接口链路摸清楚了。旧接口文档能看到免费产品码,但控制台里的免费证书额度和实例流才是目前更稳定的路径。把“检查过期、购买实例、申请证书、DNS 验证、下载替换、Nginx reload”串起来之后,后面新增域名只需要在 Cert Admin 里加一个目标。

请我喝咖啡

感谢支持,我会继续更新更有用的技术内容。

打赏二维码
请我喝咖啡 如果内容帮到了你,可以赞赏支持继续更新。
Category
Tags
Site statistics

本站现有文章203篇,共被浏览133391

本次响应耗时: 0.202s

当前来路IP: 216.73.217.0   403 Forbidden

您是本站第: 240454 位访客!

本站已苟活: 

Commercial
开发者产品赞助位开放

适合 AI 工具、云服务、课程、开源项目和招聘团队。

查看合作方案
All hots
Article archiving
Mongona Radio
等待播放