wildwindstudio.com Certificate Auto-Renewal + Tencent Cloud CLB Rebinding
On this page
Host: bj-middleware-1 (root, TencentOS, Tencent Cloud Beijing)
2026-09-20 Old Certificate Cleanup and Proxy Entry Verification Fix
The September 22nd expiry reminder pointed to YgO1W075 / YgOB9Jx4 uploaded in June. The renewal and rebinding on September 7th were indeed successful; the original script only handled upload, rebinding, and verification, lacking a step to clean up old Tencent Cloud certificates.
It was also discovered that cn-reserve-api's public entry uses an independent TrustAsia certificate, while the CLB origin server uses Let's Encrypt. The old script forcibly compared the public leaf certificate with the origin leaf certificate, falsely judging the deployment as failed, and repeatedly uploaded during daily pending retries.
The current deployment logic is implemented by /usr/local/bin/clb-cert-manager.py, with clb-deploy-hook.sh loading the original credentials and calling it:
- Retrieves the real origin entry from the CLB API, uses the business domain as SNI, and checks the origin certificate fingerprint; the public side separately verifies the trust chain, domain, and validity period, allowing the entry to use an independent certificate.
- Reuses the original certificate ID when the target certificate is already deployed; upload results are persisted, so rebinding failures on retry do not re-upload.
- Persists the old ID before rebinding; the old certificate is only cleaned up after successful verification of the new deployment.
- An independent retry queue for cleanup is run at the end of the daily orchestration. The
DeleteCertificate(IsCheckResource=true)command is only executed after confirming the old certificate's ownership/domain, current deployment health, successful asynchronous queries of all cloud resources, and no bindings. The queue entry is only removed after the asynchronous deletion succeeds. If the query fails or resources are still bound, it is retained and an error is reported. - The official service role
SSL_QCSLinkedRoleInReplaceLoadCertificatewas added with user authorization, for use in full resource checks and protected deletion. - Status:
/var/lib/clb-cert-renewal/<cert-name>.json; backup:/root/cert-renewal-fix-20260920/; logs and the 02:00 Beijing Time schedule remain unchanged.
The three current CLB certificates remain abhh3hBR (jsvc), abhYtjWf (passport), and avq1JprF (cn-reserve-api), with origin and public TLS passing actual tests. 10 tests cover prohibiting deletion when resource queries are incomplete/incorrect/still bound, asynchronous deletion retries, rebinding retries reusing uploads, and independent edge certificate verification.
After this full cloud resource scan, 16 unbound old/duplicate certificates (including YgO1W075, YgOB9Jx4) have been protected-deleted; the final list contains only the 3 active certificates mentioned above. The cleanup queue and deployment pending are both empty, the orchestration entry returns 0, and crond is running normally.
It is recommended to uniformly use renew-and-update-clb.sh [cert-name ...]. The historical emergency tool clb-bind-one.sh does not yet include the new verification/cleanup flow and is not the recommended entry point.
2026-09-07 Fix and Root Cause Correction
Automatic renewal failed in the early hours of September 1st. jsvc / passport are still using the certificate expiring on September 22nd. It has been confirmed that certbot-dns-tencentcloud 2.0.2 constructs the TC3 signature using the local date from datetime.now(). The date at 02:00 Beijing Time differs by one day from UTC. Read-only DNSPod API contrast tests reproduced the AuthFailure.SignatureFailure; requests succeed when the date is correct. The plugin also swallowed the API exception, ultimately displaying failed to determine base domain. Attributing the successful manual renewal in June to "avoiding rate limits by staggering times" lacks sufficient evidence and cannot be considered a confirmed root cause.
Current script behavior:
/etc/crontabchecks daily at 02:00 Beijing Time; only the certbot subprocess setsTZ=UTC, without modifying the server timezone.- By default, it manages the three certificates listed in the mapping table below, but can also accept one or more
cert-namearguments for targeted execution; execution remains serial, with a 60-second interval between each certificate. flockprevents duplicate executions; renewal or rebinding failures return a non-zero exit code, write logs, and log to local syslog, instead of uniformly reporting success.- The deploy-hook validates listener domain/SNI, waits for CLB asynchronous tasks to complete, rechecks the certificate ID, and verifies via HTTPS that the public leaf certificate matches the newly issued certificate.
/var/lib/clb-cert-renewal/<cert-name>.pendingmarks incomplete rebinding; even if certbot returns 0, the next execution will retry deployment first. The marker is only deleted after successful verification.- Backup: Server
/root/cert-renewal-backup-20260907/. Credential files remain unchanged.
Actual renewal results for this session (2026-09-07, script exited 0, CLB and public TLS verified):
| Domain | Old Tencent Cloud Certificate ID | New Tencent Cloud Certificate ID | New Expiry Time (Beijing Time) |
|---|---|---|---|
| jsvc.wildwindstudio.com | YgO1W075 | abhh3hBR | 2026-12-06 10:05:57 |
| passport.wildwindstudio.com | YgOB9Jx4 | abhYtjWf | 2026-12-06 10:04:03 |
cn-reserve-api was not manually renewed this time; CLB remains YToxgk9V; it remains on the original automatic management list and will be handled by subsequent daily tasks according to renewal conditions. No pending markers remained at acceptance, and crond is running normally. Tests for crossing UTC date boundaries, renewal failure exit codes, deploy-hook failure detection, pending deployment retries, and CLB API non-zero exits all passed. The next scheduled check is 02:00 Beijing Time on 2026-09-08.
Recommended manual entry point (includes UTC, locking, logging, renewal, and rebinding verification):
The following June troubleshooting records are retained for historical reference; the current mechanism is based on the fixes described above.
1. Overall Process
Logs are written uniformly to /var/log/certbot-renewal.log; certbot's own debug logs are in /var/log/letsencrypt/.
Key Design Points
- Serial execution + 60s sleep: Retains staggered timing as request rate control; it does not fix signature date errors (see correction above). Do not revert to a single
certbot renewbatch run for all certificates. - deploy-hook model: Upload/rebinding is only triggered if the certificate "actually renewed," avoiding unnecessary re-uploads of the same certificate. This is more correct than the old script (which unconditionally retried), but also means — if renewal fails, CLB will not be updated, so renewal health must be monitored (see Section 6).
2. Certificate ↔ CLB Mapping
Region is fixed to ap-beijing; currently all are single-domain certificates:
| Certificate (certbot cert-name) | Domain | CLB | Listener |
|---|---|---|---|
jsvc.wildwindstudio.com-0001 | jsvc.wildwindstudio.com | lb-fkcz6lot | lbl-ehxkll9d |
passport.wildwindstudio.com | passport.wildwindstudio.com | lb-b6j4kn47 | lbl-0jzb58rx |
cn-reserve-api.wildwindstudio.com | cn-reserve-api.wildwindstudio.com | lb-mm70ra69 | lbl-naeanxnv |
Mapping is maintained in the
casebranches ofclb-deploy-hook.sh. When adding/changing certificates, update here + theCERTS=(...)list at the top ofrenew-and-update-clb.sh.The
-0001suffix on thejsvccertificate's cert-name is a historical legacy (the old three-domain SAN lineage for the same name occupiedjsvc.wildwindstudio.com); it has no functional impact. resflow.wildwindstudio.com has been decommissioned (no DNS, no online service); its old CLBlb-mm70ra69is now reused for cn-reserve-api.
3. File Inventory
| Path | Description | Permissions |
|---|---|---|
/etc/crontab | Scheduled entry (no longer contains plaintext keys) | 644 |
/usr/local/bin/renew-and-update-clb.sh | Renewal orchestration script | 755 |
/usr/local/bin/clb-deploy-hook.sh | certbot deploy-hook: Upload + Rebind CLB | 755 |
/usr/local/bin/clb-bind-one.sh | Manual single-certificate rebinding tool (for emergencies) | 755 |
/etc/letsencrypt/tccli.env | tccli credentials (for upload/rebinding) | 600 |
/etc/letsencrypt/tencentcloud.ini | certbot DNS plugin credentials (for DNS-01 TXT write) | 600 |
/var/log/certbot-renewal.log | Renewal chain logs | - |
Note: The DNS plugin and tccli use two different sets of Tencent Cloud API Keys. The former only requires DNSPod permissions, while the latter requires SSL + CLB permissions.
4. Troubleshooting Records (2026-06-24)
Symptoms
- CLB
lb-fkcz6lot(jsvc.wildwindstudio.com) certificate expired; "renewal script had no response," and the online server was serving an expired certificate. - The same batch, passport and jsvc, both expired on 2026-06-24.
Troubleshooting Steps and Findings
- Locate entry point:
renew-and-update-clb.shruns at 02:00 on the 1st and 15th of each month in/etc/crontab, logs to/var/log/certbot-renewal.log. - Read logs: The 06-15
certbot renewfor jsvc/resflow reported
Certbot renewal was actually failing continuously.failed to determine base domain, please report to dev. Tried: ['wildwindstudio.com', 'jsvc.wildwindstudio.com'] - Read plugin source
certbot_dns_tencentcloud/certbot_tencentcloud_plugins.py'sdetermine_base_domain():
Any API exception (rate limiting / QPS / transient errors) is swallowed, eventually throwing this misleading "base domain" error.= # ← Treats any API exception as "does not own this domain" continue ... - Contrast: cn-reserve-api, same account, same
tencentcloud.ini, same root domain, succeeded when issued individually on 06-16; the only difference was "running individually vs. batch running." - staging / live reproduction: Single
certbot renew --cert-name jsvc...succeeded during the day. Based on this, it was inferred to be rate limiting, but this contrast also changed the execution time, so timezone signature differences could not be ruled out; the timezone defect was reproduced in September, see correction above. - Old script masked the issue: The old script logic before 06-16 was "regardless of certbot success or failure, unconditionally re-upload the existing certificate and rebind CLB." So every month from 04~06 showed "✓ Rebinding successful," but it was always binding the same old certificate issued on 03-26 and expiring on 06-24, creating the illusion that "renewal was working normally." After changing to the deploy-hook model on 06-16, renewal failures no longer triggered rebinding, exposing the problem.
Inferences at the Time (Corrected)
Initially suspected that adding new domains caused batch request rate limiting. In September, it was found that serial staggered timing still failed in the early hours, and the UTC date signature defect was reproduced; the "single manual success" in June cannot be taken as proof of rate limiting.
5. Actions Taken This Time
Emergency Renewal
- Manually renewed jsvc / passport serially (isolated) to avoid rate limiting; used
clb-bind-one.shto upload and rebind the corresponding CLB. - Result: Online certificates for lb-fkcz6lot and lb-b6j4kn47 were updated, valid until 2026-09-22.
June Script Modifications (Deployed at the time; timezone issue found unresolved in September)
renew-and-update-clb.shchanged to serial execution + 60s sleep per certificate +--no-random-sleep-on-renew, addressing rate limiting at the source.clb-deploy-hook.shcorrected certificate name mapping, retaining "only rebind after true renewal."
Cleanup
- Deleted expired redundant jsvc three-domain SAN lineage (including decommissioned resflow).
- Deleted old
.bakscripts containing plaintext keys. - Moved plaintext tccli key from 644
/etc/crontabto 600/etc/letsencrypt/tccli.env; all three scripts now read from this uniformly; crontab backup permissions downgraded to 600.
6. Operations Runbook
Manual Renewal + Rebind a Single Certificate (Emergency)
# 1) Renew (must run single, isolated, do not run multiple at once)
TZ=UTC
# 2) Upload + Rebind (credentials automatically read from /etc/letsencrypt/tccli.env)
Verify CLB Online Certificate
| |
View Certificate List / Expiry
Add a New Domain/Certificate
certbot certonly --dns-tencentcloud --dns-tencentcloud-credentials /etc/letsencrypt/tencentcloud.ini -d <domain>- Add mapping in the
caseofclb-deploy-hook.sh; add cert-name toCERTS=(...)inrenew-and-update-clb.sh. - Manually bind once with
clb-bind-one.sh.
TODOs / Improvements
-
Rotate tccli API Key (
AKIDqgLrCLI2...): Was stored in plaintext in 644 crontab for months; treat as compromised. - Add certificate days-remaining alert (integrate with existing Prometheus + DingTalk); alert when < 14 days remaining; stop relying on "script didn't error."
-
Consider adding retry/backoff to the
certbot-dns-tencentcloudplugin (note: modifying site-packages will be overwritten on upgrade; serial staggered timing itself does not solve timezone or other API errors).