Automate Let’s Encrypt Renewal & Sync for Qiniu CDN using acme.sh & DNSPod
This guide walks through setting up acme.sh with DNSPod to perform DNS‑01 validation, automatically obtain Let’s Encrypt certificates, and use hook scripts to upload them to Qiniu CDN, update HTTPS settings, and clean up old certificates, eliminating manual renewal steps.
Problem Overview
When using Qiniu Kodo object storage for a Mini‑Program, the CDN domain (e.g., img.example.com) must serve HTTPS. Qiniu CDN cannot answer Let’s Encrypt HTTP‑01 challenges, so DNS‑01 is required, and manual upload of renewed certificates is cumbersome.
Preparation
Install acme.sh
curl https://get.acme.sh | sh
~/.acme.sh/acme.sh --set-default-ca --server letsencryptObtain DNSPod and Qiniu API keys
Create a DNSPod API key (DP_Id, DP_Key) from the DNSPod console and a Qiniu AccessKey/SecretKey from the personal center. Store them in /etc/ssl/.cdn-env with permissions chmod 600.
Certificate Issuance
Export the keys and run acme.sh with the DNSPod DNS‑01 hook:
source /etc/ssl/.cdn-env
export DP_Id DP_Key
DOMAIN="img.example.com"
~/.acme.sh/acme.sh --issue -d "$DOMAIN" --dns dns_dp --server letsencryptacme.sh creates the _acme-challenge TXT record via DNSPod, validates the domain, and downloads the certificate.
Install Certificate and Set Renewal Hook
BASE_DIR="/opt/ssl"
DOMAIN="img.example.com"
mkdir -p "${BASE_DIR}/${DOMAIN}"
~/.acme.sh/acme.sh --install-cert -d "$DOMAIN" \
--fullchain-file "${BASE_DIR}/${DOMAIN}/fullchain.pem" \
--key-file "${BASE_DIR}/${DOMAIN}/privkey.pem" \
--reloadcmd "bash /usr/local/bin/on-cert-renew.sh qiniu"The --reloadcmd ensures the following hook script runs after each successful renewal.
Renewal Hook Script
The hook receives a mode argument ( qiniu) and invokes a Python program that synchronises the new certificate to Qiniu.
#!/usr/bin/env bash
set -euo pipefail
MODE="${1:-}"
LOG_FILE="/var/log/ssl/renewal.log"
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE"; }
case "$MODE" in
qiniu)
log "[qiniu] Certificate renewed, syncing to Qiniu CDN..."
if python3 /usr/local/bin/qiniu-cert-sync.py >>"$LOG_FILE" 2>&1; then
log "[qiniu] Sync succeeded"
else
log "[qiniu] Sync failed, check log"
exit 1
fi
;;
*)
log "Unknown mode: $MODE"
exit 1
;;
esacQiniu Authentication
Two authentication schemes are required:
QBox authentication (certificate upload & source‑domain config)
def b64url(data: bytes) -> str:
return base64.urlsafe_b64encode(data).decode()
def qbox_token(ak: str, sk: str, path_with_query: str) -> str:
sign_str = path_with_query + "
"
sig = hmac.new(sk.encode(), sign_str.encode(), hashlib.sha1).digest()
return f"QBox {ak}:{b64url(sig)}"Qiniu authentication (CDN domain HTTPS config)
def qiniu_token(ak, sk, method, host, path, content_type, body=None):
date_str = datetime.now(timezone.utc).strftime("%Y%m%dT%H%M%SZ")
sign_str = f"{method}{path}
Host: {host}
"
if content_type and body is not None:
sign_str += f"Content-Type: {content_type}
"
sign_str += f"X-Qiniu-Date: {date_str}
"
if body:
sign_str += body
sig = hmac.new(sk.encode(), sign_str.encode(), hashlib.sha1).digest()
return f"Qiniu {ak}:{b64url(sig)}", date_strBoth tokens are used in the subsequent API calls.
Synchronization Script (Python)
The script performs four steps: upload the certificate, update the source‑origin domain, update the CDN domain HTTPS configuration, and clean up old certificates.
Upload certificate – POST to /sslcert with name, commonName, pri, and ca. The response provides certID for later use.
Update CDN HTTPS config – PUT to /domain/{DOMAIN}/httpsconf with certId, http2Enable, forceHttps, and tlsVersions set to "TLSv1.2" (allows TLS 1.2/1.3, satisfies Mini‑Program requirements).
Cleanup old certificates – List certificates (limit 50), keep the newly uploaded certID, and delete others. Errors with code 400611 indicate the certificate is still in use; the script skips those deletions.
Cron Scheduling
Although acme.sh includes its own scheduler, a manual cron entry guarantees a daily check:
(crontab -l 2>/dev/null | grep -v 'acme.sh' || true) | crontab - 2>/dev/null || true
(crontab -l 2>/dev/null; printf '%s
' '0 0 * * * "/root/.acme.sh/acme.sh" --cron --home "/root/.acme.sh" --log "/var/log/ssl/renewal.log" 2>&1') | crontab -Result Example
[2026-05-27 10:23:13] === Qiniu cert sync start: img.example.com ===
[2026-05-27 10:23:13] 检测到证书变更,开始同步...
[2026-05-27 10:23:13] 上传证书成功,certID = 6a165591...
[2026-05-27 10:23:14] 更新源站域名配置成功
[2026-05-27 10:23:14] 更新 CDN 域名配置成功
[2026-05-27 10:23:14] 清理旧证书:无旧证书
[2026-05-27 10:23:14] 同步完成If the script runs again without a certificate change, it logs “证书未变更,跳过同步”.
Conclusion
The complete solution automates Let’s Encrypt DNS‑01 validation via DNSPod, installs the certificate with acme.sh, and synchronises it to Qiniu CDN using authenticated API calls, while also handling TLS version constraints and safe removal of obsolete certificates.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
