Aliyun OSS 静态站点自动化 HTTPS 证书续期 链接到标题

背景 链接到标题

阿里云免费 SSL 证书配额耗尽,无法通过 API 申请新证书。OSS 自定义域名的 HTTPS 证书绑定只能通过控制台手动操作,无法自动化。

目标:实现免费证书的全自动管理,包括申请、续期和部署。

架构 链接到标题

客户端 → DNS(CNAME) → 阿里云 CDN(HTTPS 终止) → OSS 源站
                                            ↑
                                    部署节点 certbot
                                    cert.pem → SetCdnDomainSSLCertificate

流量路径:

  • 用户访问 https://www.example.com
  • DNS 解析到 CDN CNAME(www.example.com.w.kunlunaq.com
  • CDN 边缘节点处理 HTTPS,证书由 SetCdnDomainSSLCertificate 管理
  • CDN 回源到 OSS bucket(example-bucket.oss-cn-beijing.aliyuncs.com

关键发现:www.example.com 在阿里云系统中已是 CDN 域名,只是 DNS CNAME 未指向 CDN,导致流量直连 OSS 绕过 CDN。

实施步骤 链接到标题

环境准备 链接到标题

  • 部署节点:Ubuntu 22.04,有公网访问
  • 安装 aliyun CLI(v3.4.10)
  • 安装 certbot(snap 安装,v5.7.0)
  • 配置 aliyun CLI 凭据(AccessKey 需 AlidnsFullAccess 权限)

三个脚本 链接到标题

1. certbot-auth.sh — DNS-01 验证时添加 TXT 记录

#!/bin/bash
source /home/ecs-user/traefik/.env
aliyun alidns AddDomainRecord \
  --DomainName example.com \
  --RR _acme-challenge.www \
  --Type TXT \
  --Value "$CERTBOT_VALIDATION" \
  --TTL 600
sleep 120

注意事项:

  • 阿里云 DNS 最小 TTL 为 600 秒,不能设更低
  • sleep 120 秒等待 DNS propagation(实际效果取决于 DNS 服务商刷新速度)

2. certbot-cleanup.sh — 验证后清理 TXT 记录

#!/bin/bash
source /home/ecs-user/traefik/.env
RECORD_ID=$(aliyun alidns DescribeDomainRecords \
  --DomainName example.com \
  --RRKeyWord _acme-challenge.www \
  --TypeKeyWord TXT \
  --ValueKeyWord "$CERTBOT_VALIDATION" 2>/dev/null | \
  python3 -c 'import json,sys; d=json.load(sys.stdin); rs=d.get("DomainRecords",{}).get("Record",[]); print(rs[0]["RecordId"] if rs else "")')
if [[ -n "$RECORD_ID" ]]; then
  aliyun alidns DeleteDomainRecord --RecordId "$RECORD_ID"
fi

3. certbot-deploy.sh — 证书续期后部署到 CDN

#!/bin/bash
if [[ "$RENEWED_DOMAINS" == *"www.example.com"* ]]; then
  source /home/ecs-user/traefik/.env
  aliyun cdn SetCdnDomainSSLCertificate \
    --DomainName www.example.com \
    --SSLProtocol on \
    --CertType upload \
    --SSLPub "$(cat "$RENEWED_LINEAGE/fullchain.pem")" \
    --SSLPri "$(cat "$RENEWED_LINEAGE/privkey.pem")" \
    --CertName "example-le-$(date +%Y%m%d)"
fi

首次签发 链接到标题

sudo certbot certonly \
  --authenticator manual \
  --preferred-challenges dns \
  --manual-auth-hook /usr/local/bin/certbot-auth.sh \
  --manual-cleanup-hook /usr/local/bin/certbot-cleanup.sh \
  --deploy-hook /usr/local/bin/certbot-deploy.sh \
  -d www.example.com \
  --email user@example.com \
  --agree-tos --no-eff-email

证书存放于 /etc/letsencrypt/live/www.example.com/,有效期为 90 天。

DNS 切换 链接到标题

www.example.com 的 CNAME 从 OSS 域名改为 CDN CNAME:

aliyun alidns DeleteDomainRecord --RecordId <旧记录ID>
aliyun alidns AddDomainRecord \
  --DomainName example.com \
  --RR www \
  --Type CNAME \
  --Value "www.example.com.w.kunlunaq.com" \
  --TTL 600

切换后约 5-10 分钟生效(TTL 600 秒)。

自动续期 链接到标题

Certbot snap 包自带 systemd timer,每天自动检查续期:

systemctl list-timers | grep certbot
# snap.certbot.renew.timer

续期流程:

  1. 添加 _acme-challenge.www TXT 记录
  2. Let’s Encrypt 验证
  3. 清理 TXT 记录
  4. 调用 SetCdnDomainSSLCertificate 更新 CDN 证书

无需额外 cron 配置,certbot 会自动处理。

验证 链接到标题

# 查看证书
sudo openssl x509 -in /etc/letsencrypt/live/www.example.com/cert.pem -noout -subject -issuer -dates

# 验证 HTTPS
curl -I https://www.example.com
echo | openssl s_client -connect www.example.com:443 -servername www.example.com 2>/dev/null | openssl x509 -noout -subject -issuer -dates

问题排查 链接到标题

问题 原因 解决
region can’t be empty certbot hook 以 root 运行,但 aliyun CLI 配置在用户目录 将配置复制到 /root/.aliyun/ 或在 hook 中 source 用户凭据
QuotaExceeded.TTL DNS TTL 设得太低(<600) TTL 改为 600 或更高
InvalidParameter Value TXT 记录值格式问题 检查引号转义,确保值正确传递
too many failed authorizations Let’s Encrypt 每小时限 5 次失败 等待 1 小时后重试
SERVFAIL DNS 未及时刷新 增加 sleep 时间或检查 DNS 服务商

参考 链接到标题