{"id":1875,"date":"2025-11-15T18:05:04","date_gmt":"2025-11-15T15:05:04","guid":{"rendered":"https:\/\/www.dchost.com\/blog\/redundant-acme-automation-that-just-works-acme-sh-fallback-from-lets-encrypt-to-zerossl\/"},"modified":"2025-11-15T18:05:04","modified_gmt":"2025-11-15T15:05:04","slug":"redundant-acme-automation-that-just-works-acme-sh-fallback-from-lets-encrypt-to-zerossl","status":"publish","type":"post","link":"https:\/\/www.dchost.com\/blog\/en\/redundant-acme-automation-that-just-works-acme-sh-fallback-from-lets-encrypt-to-zerossl\/","title":{"rendered":"Redundant ACME Automation That Just Works: acme.sh Fallback from Let\u2019s Encrypt to ZeroSSL"},"content":{"rendered":"<div class=\"dchost-blog-content-wrapper\"><p>It was one of those quiet Sundays when everything should have been boring. Coffee in hand, inbox under control, servers humming. Then my phone buzzed: \u201cRenewal failed. Certificate expires in 2 days.\u201d My stomach did that tiny flop it always does when something simple decides to be dramatic. The culprit? A brief blip on an ACME endpoint. Nothing catastrophic, but just loud enough to break a clean renewal cycle. And that was the moment I promised myself I\u2019d never rely on a single certificate authority again, no matter how reliable they usually are.<\/p>\n<p>If you\u2019ve ever had that oh-no-not-SSL-again moment, this guide is my friendly nudge toward a calmer setup: using <strong>acme.sh<\/strong> with a clean, pragmatic <strong>fallback from Let\u2019s Encrypt to ZeroSSL<\/strong>. I\u2019ll walk you through the why and the how, the little caveats, and a workflow that scales from one tiny blog to a fleet of multi-tenant domains. We\u2019ll set up accounts with both CAs, automate renewals, and wrap it all in a tiny safety net so you can sleep through the night while your certs renew themselves without drama.<\/p>\n<div id=\"toc_container\" class=\"toc_transparent no_bullets\"><p class=\"toc_title\">\u0130&ccedil;indekiler<\/p><ul class=\"toc_list\"><li><a href=\"#Why_Redundancy_for_ACME_Actually_Matters\"><span class=\"toc_number toc_depth_1\">1<\/span> Why Redundancy for ACME Actually Matters<\/a><\/li><li><a href=\"#Meet_acmesh_and_the_Idea_of_CA_Fallback\"><span class=\"toc_number toc_depth_1\">2<\/span> Meet acme.sh and the Idea of CA Fallback<\/a><\/li><li><a href=\"#Validation_That_Doesnt_Bite_Why_DNS01_Quietly_Wins\"><span class=\"toc_number toc_depth_1\">3<\/span> Validation That Doesn\u2019t Bite: Why DNS\u201101 Quietly Wins<\/a><\/li><li><a href=\"#The_Plan_A_Calm_Fallback_Workflow\"><span class=\"toc_number toc_depth_1\">4<\/span> The Plan: A Calm Fallback Workflow<\/a><\/li><li><a href=\"#Step-by-Step_acmesh_with_Lets_Encrypt_First_ZeroSSL_as_Fallback\"><span class=\"toc_number toc_depth_1\">5<\/span> Step-by-Step: acme.sh with Let\u2019s Encrypt First, ZeroSSL as Fallback<\/a><ul><li><a href=\"#1_Install_acmesh\"><span class=\"toc_number toc_depth_2\">5.1<\/span> 1) Install acme.sh<\/a><\/li><li><a href=\"#2_Register_ACME_accounts_with_both_CAs\"><span class=\"toc_number toc_depth_2\">5.2<\/span> 2) Register ACME accounts with both CAs<\/a><\/li><li><a href=\"#3_Configure_DNS01_credentials\"><span class=\"toc_number toc_depth_2\">5.3<\/span> 3) Configure DNS\u201101 credentials<\/a><\/li><li><a href=\"#4_Issue_your_first_certificate_with_Lets_Encrypt\"><span class=\"toc_number toc_depth_2\">5.4<\/span> 4) Issue your first certificate with Let\u2019s Encrypt<\/a><\/li><li><a href=\"#5_Add_a_tiny_CAfallback_wrapper\"><span class=\"toc_number toc_depth_2\">5.5<\/span> 5) Add a tiny CA\u2011fallback wrapper<\/a><\/li><li><a href=\"#6_Run_it_on_a_timer_not_a_hope\"><span class=\"toc_number toc_depth_2\">5.6<\/span> 6) Run it on a timer, not a hope<\/a><\/li><\/ul><\/li><li><a href=\"#Testing_Without_Tears\"><span class=\"toc_number toc_depth_1\">6<\/span> Testing Without Tears<\/a><\/li><li><a href=\"#Scaling_the_Pattern_for_Many_Domains_or_Tenants\"><span class=\"toc_number toc_depth_1\">7<\/span> Scaling the Pattern for Many Domains or Tenants<\/a><\/li><li><a href=\"#A_Few_Real-World_Gotchas_and_Easy_Fixes\"><span class=\"toc_number toc_depth_1\">8<\/span> A Few Real-World Gotchas (and Easy Fixes)<\/a><\/li><li><a href=\"#What_About_Staging_Canaries_and_Safe_Rollouts\"><span class=\"toc_number toc_depth_1\">9<\/span> What About Staging, Canaries, and Safe Rollouts?<\/a><\/li><li><a href=\"#How_Fallback_Plays_with_Rate_Limits_and_Issuance_Bursts\"><span class=\"toc_number toc_depth_1\">10<\/span> How Fallback Plays with Rate Limits and Issuance Bursts<\/a><\/li><li><a href=\"#A_Note_on_Secrets_and_Safe_Storage\"><span class=\"toc_number toc_depth_1\">11<\/span> A Note on Secrets and Safe Storage<\/a><\/li><li><a href=\"#ZeroSSL_vs_Lets_Encrypt_The_Narrative_That_Actually_Matters\"><span class=\"toc_number toc_depth_1\">12<\/span> ZeroSSL vs Let\u2019s Encrypt: The Narrative That Actually Matters<\/a><\/li><li><a href=\"#Rolling_Back_Gracefully_If_You_Need_To\"><span class=\"toc_number toc_depth_1\">13<\/span> Rolling Back Gracefully If You Need To<\/a><\/li><li><a href=\"#When_DNS_Is_Your_Bottleneck\"><span class=\"toc_number toc_depth_1\">14<\/span> When DNS Is Your Bottleneck<\/a><\/li><li><a href=\"#Operational_Comfort_Checklists_That_Actually_Work\"><span class=\"toc_number toc_depth_1\">15<\/span> Operational Comfort: Checklists That Actually Work<\/a><\/li><li><a href=\"#One_More_Quiet_Edge_Serving_Dual_Certs\"><span class=\"toc_number toc_depth_1\">16<\/span> One More Quiet Edge: Serving Dual Certs<\/a><\/li><li><a href=\"#The_Wrap-Up_Calm_Renewals_No_Drama\"><span class=\"toc_number toc_depth_1\">17<\/span> The Wrap-Up: Calm Renewals, No Drama<\/a><\/li><\/ul><\/div>\n<h2 id=\"section-1\"><span id=\"Why_Redundancy_for_ACME_Actually_Matters\">Why Redundancy for ACME Actually Matters<\/span><\/h2>\n<p>On a good day, automated SSL is so invisible you forget it exists. On a weird day, a rate limit, DNS hiccup, or a chain update can make it feel like you\u2019re changing tires on a moving car. Let\u2019s Encrypt and ZeroSSL are both excellent, but even excellent systems have edges. Maybe you hit a burst of new domains and brushed a rate limit. Maybe the domain\u2019s DNS took an extra minute to propagate. Or maybe a regional network glitch briefly blocks an endpoint. It doesn\u2019t have to be dramatic to be disruptive.<\/p>\n<p>Here\u2019s the thing: ACME automation is only as reliable as the <strong>weakest part of your chain<\/strong>. If your validation relies on a single provider, a single DNS API, or a single CA, a tiny snag can stall renewals. Redundancy isn\u2019t about being paranoid; it\u2019s about being practical. You don\u2019t have to overhaul your whole workflow\u2014just add one extra lane. If Let\u2019s Encrypt stumbles, ZeroSSL steps in. Renewals continue, your web servers reload, and your Sunday stays boring in the best possible way.<\/p>\n<p>And if you\u2019re wrangling lots of domains or a multi-tenant SaaS, the value of redundancy multiplies. A single CA outage can snowball into a customer support day you didn\u2019t plan for. Building in fallback gives you predictable outcomes. If you\u2019re curious how DNS challenges scale in those environments, I shared a detailed story in <a href=\"https:\/\/www.dchost.com\/blog\/en\/saaste-ozel-alan-adlari-ve-otomatik-ssl-dns%E2%80%9101-ile-cok-kiracili-mimarini-nasil-tatli-tatli-olceklersin\/\">Bring Your Own Domain, Get Auto\u2011SSL: How DNS\u201101 ACME Scales Multi\u2011Tenant SaaS Without Drama<\/a>.<\/p>\n<h2 id=\"section-2\"><span id=\"Meet_acmesh_and_the_Idea_of_CA_Fallback\">Meet acme.sh and the Idea of CA Fallback<\/span><\/h2>\n<p>If you\u2019ve used certbot or another ACME client, acme.sh will feel refreshingly straightforward. It\u2019s a shell script that speaks ACME fluently and integrates with a long list of DNS providers for DNS\u201101 challenges. The magic feature for our story is that <strong>acme.sh supports multiple certificate authorities<\/strong> and lets you switch between them with a single flag. You can register accounts with Let\u2019s Encrypt and ZeroSSL, choose one as your default, and then purposely fail over to the other when needed. Simple ingredients, powerful result.<\/p>\n<p>In my experience, the best approach is to make Let\u2019s Encrypt your first choice and ZeroSSL your backup. Why that order? Familiarity, widespread tooling, and muscle memory. But the point is not which one is first\u2014the point is you have two. When one path has a pebble in the road, you keep moving by taking the other path.<\/p>\n<p>If you want to glance at the project straight from the source, the <a href=\"https:\/\/github.com\/acmesh-official\/acme.sh\" rel=\"nofollow noopener\" target=\"_blank\">acme.sh documentation and repository<\/a> is a helpful companion as you follow along here.<\/p>\n<h2 id=\"section-3\"><span id=\"Validation_That_Doesnt_Bite_Why_DNS01_Quietly_Wins\">Validation That Doesn\u2019t Bite: Why DNS\u201101 Quietly Wins<\/span><\/h2>\n<p>Let\u2019s talk validation, because it\u2019s the part that bites when you least expect it. HTTP\u201101 is fine until you throw in a CDN, a container cluster rolling out new images, or an edge cache serving stale content. Suddenly your challenge file is somewhere in limbo and the CA can\u2019t see it. DNS\u201101 is different. You prove domain control by publishing a TXT record. No worries about reverse proxies, no port 80 surprises, and it scales gracefully across many tenants and subdomains.<\/p>\n<p>With acme.sh, you pick a DNS provider plugin (Cloudflare, Route53, DigitalOcean, and many others) and supply credentials once. After that, the script manages the TXT records automatically. It\u2019s boring in the best way. And if you\u2019re running a multi-tenant setup, DNS\u201101 gives you sane automation without trying to smuggle challenge files past a dozen load balancers. I wrote about that pattern and why it feels almost unfairly smooth in <a href=\"https:\/\/www.dchost.com\/blog\/en\/saaste-ozel-alan-adlari-ve-otomatik-ssl-dns%E2%80%9101-ile-cok-kiracili-mimarini-nasil-tatli-tatli-olceklersin\/\">Bring Your Own Domain, Get Auto\u2011SSL: How DNS\u201101 ACME Scales Multi\u2011Tenant SaaS Without Drama<\/a>.<\/p>\n<p>One more subtle point: if you rely on TXT records for challenges, the reliability of your DNS matters. I\u2019m a fan of a multi\u2011provider DNS setup so you\u2019re not betting renewals on a single vendor. If that sounds interesting, I shared how I do it in <a href=\"https:\/\/www.dchost.com\/blog\/en\/coklu-saglayici-dns-nasil-kurulur-octodns-ile-zero%E2%80%91downtime-gecis-ve-dayaniklilik-rehberi\/\">How I Run Multi\u2011Provider DNS with octoDNS (and Sleep Through Migrations)<\/a>. You don\u2019t have to go that far on day one, but it\u2019s a nice move when you\u2019re aiming for \u201cset it and forget it\u201d reliability.<\/p>\n<h2 id=\"section-4\"><span id=\"The_Plan_A_Calm_Fallback_Workflow\">The Plan: A Calm Fallback Workflow<\/span><\/h2>\n<p>Here\u2019s the workflow that has served me and my clients well:<\/p>\n<p>First, create ACME accounts with both Let\u2019s Encrypt and ZeroSSL. ZeroSSL asks for an EAB key pair (think of it as a pair of tokens that bind your ACME account to your ZeroSSL account). You add those once and move on. Second, pick DNS\u201101 with a provider API that\u2019s stable for you. Third, issue your certificate with Let\u2019s Encrypt like you normally would. Fourth, add a tiny wrapper script around acme.sh. That script tries to renew with Let\u2019s Encrypt, and if it fails\u2014because of a transient issue, a rate limit, or anything else\u2014it switches to ZeroSSL, renews, reloads your web server, and logs the failover so you know it happened. That\u2019s it. Nothing fancy, just a second brake pedal when the first gets soft.<\/p>\n<p>A little tip I learned the hard way: always test your flow in a staging environment so you don\u2019t accidentally trigger rate limits while experimenting. The <a href=\"https:\/\/letsencrypt.org\/docs\/staging-environment\/\" rel=\"nofollow noopener\" target=\"_blank\">Let\u2019s Encrypt staging environment<\/a> is perfect for this. Run your first dry runs there, verify that your DNS updates, your hooks fire, and your web server reloads cleanly. Then switch to production.<\/p>\n<h2 id=\"section-5\"><span id=\"Step-by-Step_acmesh_with_Lets_Encrypt_First_ZeroSSL_as_Fallback\">Step-by-Step: acme.sh with Let\u2019s Encrypt First, ZeroSSL as Fallback<\/span><\/h2>\n<h3><span id=\"1_Install_acmesh\">1) Install acme.sh<\/span><\/h3>\n<p>Acme.sh is just a shell script with minimal dependencies. Install it as your own user (not root), then it can deploy certs anywhere with a reload hook.<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\">curl https:\/\/get.acme.sh | sh -s email=my-email@example.com\n# or update an existing install\n~\/.acme.sh\/acme.sh --upgrade --auto-upgrade<\/code><\/pre>\n<p>Log out\/in or source your profile to get the acme.sh command in your PATH. Alternatively, call it explicitly with ~\/.acme.sh\/acme.sh.<\/p>\n<h3><span id=\"2_Register_ACME_accounts_with_both_CAs\">2) Register ACME accounts with both CAs<\/span><\/h3>\n<p>Start with Let\u2019s Encrypt:<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\">acme.sh --register-account -m my-email@example.com --server letsencrypt<\/code><\/pre>\n<p>Then register with ZeroSSL. You\u2019ll need EAB credentials from your ZeroSSL account dashboard. If you haven\u2019t grabbed them before, ZeroSSL explains how to do that here: <a href=\"https:\/\/zerossl.com\/documentation\/acme\/\" rel=\"nofollow noopener\" target=\"_blank\">ZeroSSL EAB guide<\/a>. Once you have the kid and hmac key, run:<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\">acme.sh --register-account -m my-email@example.com \n  --server zerossl \n  --eab-kid &quot;YOUR_EAB_KID&quot; \n  --eab-hmac-key &quot;YOUR_EAB_HMAC&quot;<\/code><\/pre>\n<p>Now you\u2019re ready to talk to both CAs. You can set Let\u2019s Encrypt as default for new orders:<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\">acme.sh --set-default-ca --server letsencrypt<\/code><\/pre>\n<h3><span id=\"3_Configure_DNS01_credentials\">3) Configure DNS\u201101 credentials<\/span><\/h3>\n<p>Pick your DNS provider plugin. As an example, Cloudflare is \u201cdns_cf\u201d and uses an API token:<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\">export CF_Token=&quot;your-cloudflare-token&quot;\nexport CF_Account_ID=&quot;optional-if-needed&quot;\nexport CF_Zone_ID=&quot;optional-if-needed&quot;<\/code><\/pre>\n<p>acme.sh supports many providers with simple environment variables. Add them to a secure file and source it from cron or systemd. Keep this boring and safe.<\/p>\n<h3><span id=\"4_Issue_your_first_certificate_with_Lets_Encrypt\">4) Issue your first certificate with Let\u2019s Encrypt<\/span><\/h3>\n<p>Let\u2019s say we want both the apex and wildcard so everything under your domain is covered. That\u2019s a nice fit for DNS\u201101:<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\"># RSA certificate (default)\nacme.sh --issue \n  --server letsencrypt \n  --dns dns_cf \n  -d example.com -d *.example.com<\/code><\/pre>\n<p>Optionally, issue an ECDSA cert for modern clients, while keeping RSA for the long tail. It\u2019s a trick I like because it balances speed with compatibility. If that\u2019s new to you, I wrote about serving both cleanly in <a href=\"https:\/\/www.dchost.com\/blog\/en\/nginx-apachede-ecdsa-rsa-ikili-ssl-uyumluluk-mu-hiz-mi-ikisini-birden-nasil-alirsin\/\">The Sweet Spot for Speed and Compatibility: Serving Dual ECDSA + RSA Certificates on Nginx and Apache<\/a>.<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\"># ECDSA certificate (parallel to RSA)\nacme.sh --issue \n  --server letsencrypt \n  --dns dns_cf \n  -d example.com -d *.example.com \n  -k ec-256<\/code><\/pre>\n<p>Install the certificate to paths your web server expects, and add a reload hook so renewals take effect automatically:<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\"># RSA install\nacme.sh --install-cert -d example.com \n  --key-file      \/etc\/ssl\/example.com\/rsa.key \n  --fullchain-file \/etc\/ssl\/example.com\/rsa.fullchain.pem \n  --reloadcmd     &quot;systemctl reload nginx&quot;\n\n# ECDSA install\nacme.sh --install-cert -d example.com --ecc \n  --key-file      \/etc\/ssl\/example.com\/ecdsa.key \n  --fullchain-file \/etc\/ssl\/example.com\/ecdsa.fullchain.pem \n  --reloadcmd     &quot;systemctl reload nginx&quot;<\/code><\/pre>\n<p>From here, acme.sh can renew without extra arguments. But we want a safety net.<\/p>\n<h3><span id=\"5_Add_a_tiny_CAfallback_wrapper\">5) Add a tiny CA\u2011fallback wrapper<\/span><\/h3>\n<p>acme.sh lets you pick the server per command or set a default for new orders. For a clean fallback, I like a wrapper that tries Let\u2019s Encrypt first and, if that fails, switches to ZeroSSL and forces a re-issue. After a successful fallback, it flips the default back to Let\u2019s Encrypt for the next cycle. Simple and easy to reason about.<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\">#!\/usr\/bin\/env bash\nset -euo pipefail\n\nDOMAINS=(\n  &quot;example.com&quot;\n)\n\n# Source DNS credentials and any acme.sh env\nsource \/etc\/acme.d\/env.sh\n\nACME=~\/.acme.sh\/acme.sh\nLOG=\/var\/log\/acme-fallback.log\nTS() { date -u +&quot;%Y-%m-%dT%H:%M:%SZ&quot;; }\n\nfallback_issue() {\n  local d=$1\n  echo &quot;$(TS) [WARN] LE renew failed for $d, trying ZeroSSL...&quot; | tee -a &quot;$LOG&quot;\n  $ACME --set-default-ca --server zerossl\n\n  # RSA\n  $ACME --issue -d &quot;$d&quot; -d &quot;*.${d}&quot; --dns dns_cf --force || return 1\n  $ACME --install-cert -d &quot;$d&quot; \n    --key-file \/etc\/ssl\/${d}\/rsa.key \n    --fullchain-file \/etc\/ssl\/${d}\/rsa.fullchain.pem \n    --reloadcmd &quot;systemctl reload nginx&quot;\n\n  # ECDSA (if you use dual certs)\n  $ACME --issue -d &quot;$d&quot; -d &quot;*.${d}&quot; --dns dns_cf -k ec-256 --force || return 1\n  $ACME --install-cert -d &quot;$d&quot; --ecc \n    --key-file \/etc\/ssl\/${d}\/ecdsa.key \n    --fullchain-file \/etc\/ssl\/${d}\/ecdsa.fullchain.pem \n    --reloadcmd &quot;systemctl reload nginx&quot;\n\n  echo &quot;$(TS) [INFO] ZeroSSL fallback succeeded for $d&quot; | tee -a &quot;$LOG&quot;\n  $ACME --set-default-ca --server letsencrypt\n}\n\ntry_renew() {\n  local d=$1\n  echo &quot;$(TS) [INFO] Attempting LE renew for $d&quot; | tee -a &quot;$LOG&quot;\n  set +e\n  $ACME --set-default-ca --server letsencrypt\n  $ACME --renew -d &quot;$d&quot;\n  local rc=$?\n  set -e\n  if [[ $rc -ne 0 ]]; then\n    fallback_issue &quot;$d&quot; || { echo &quot;$(TS) [ERROR] Fallback failed for $d&quot; | tee -a &quot;$LOG&quot;; return 1; }\n  else\n    echo &quot;$(TS) [INFO] LE renew OK for $d&quot; | tee -a &quot;$LOG&quot;\n  fi\n}\n\nfor d in &quot;${DOMAINS[@]}&quot;; do\n  try_renew &quot;$d&quot;\ndone<\/code><\/pre>\n<p>A couple of notes. First, I\u2019m re\u2011issuing on fallback with <strong>&#8211;force<\/strong> to switch CA cleanly if the prior order is pinned to the other server. Second, the script renews both RSA and ECDSA to keep them in sync. If you only use one key type, trim those lines. Third, each domain\u2019s install step writes to stable paths and triggers a reload. That makes your web server agnostic to which CA issued the cert. Your server just sees the latest files; the source doesn\u2019t matter.<\/p>\n<h3><span id=\"6_Run_it_on_a_timer_not_a_hope\">6) Run it on a timer, not a hope<\/span><\/h3>\n<p>acme.sh installs its own cron by default, and that\u2019s great. If you use systemd, I like a dedicated timer because the logs are tidy and scheduling is explicit. The key is to run the job with a daily cadence and a random delay so a thousand machines don\u2019t stampede at the same minute. Even a small jitter makes your life easier when you scale.<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\"># \/etc\/systemd\/system\/acme-fallback.service\n[Unit]\nDescription=ACME renew with fallback\n\n[Service]\nType=oneshot\nUser=acme\nGroup=acme\nExecStart=\/usr\/local\/sbin\/acme-fallback.sh\n\n# \/etc\/systemd\/system\/acme-fallback.timer\n[Unit]\nDescription=Daily ACME renew with fallback (jittered)\n\n[Timer]\nOnCalendar=daily\nRandomizedDelaySec=1800\nPersistent=true\n\n[Install]\nWantedBy=timers.target<\/code><\/pre>\n<p>Enable and start the timer, and you\u2019re off:<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\">systemctl daemon-reload\nsystemctl enable --now acme-fallback.timer<\/code><\/pre>\n<h2 id=\"section-6\"><span id=\"Testing_Without_Tears\">Testing Without Tears<\/span><\/h2>\n<p>Before you trust any automation, make it prove itself. I keep a \u201ccanary\u201d domain whose only job is to renew first. If the canary renews, I\u2019m confident the rest will follow. During setup, point your script at the <a href=\"https:\/\/letsencrypt.org\/docs\/staging-environment\/\" rel=\"nofollow noopener\" target=\"_blank\">Let\u2019s Encrypt staging environment<\/a> for a dry run, then flip to production when you\u2019re happy. A canary catches obvious issues: missing DNS credentials, a reload hook that points to the wrong service name, or a file path that\u2019s off by one directory.<\/p>\n<p>acme.sh also gives you a quick snapshot of what\u2019s due for renewal. It\u2019s not fancy, but it\u2019s helpful when you\u2019re spot-checking:<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\">acme.sh --list<\/code><\/pre>\n<p>When I\u2019m rolling this out for a client, I\u2019ll temporarily shorten the renew window so we exercise the path multiple times in a week. Nothing builds confidence like watching it quietly succeed on repeat.<\/p>\n<h2 id=\"section-7\"><span id=\"Scaling_the_Pattern_for_Many_Domains_or_Tenants\">Scaling the Pattern for Many Domains or Tenants<\/span><\/h2>\n<p>Once you\u2019ve got one domain happily renewing with fallback, expanding isn\u2019t hard. The domain list in your wrapper script can be driven by a file or pulled from an internal service. The logic doesn\u2019t change. If you run a multi-tenant platform, you\u2019ll eventually think about how many domains go into a single certificate. Combining related hostnames can be neat, but there\u2019s a limit to how big you want that list to be. Too many names in one cert can make deployments heavier, and a single validation failure can block the whole set.<\/p>\n<p>If you do hit bursts of new domains, rate limits become part of the art. There are gentle ways to avoid those walls, like issuing at a steady pace, using SANs where they make sense, and reusing validations. I shared some calm strategies in <a href=\"https:\/\/www.dchost.com\/blog\/en\/lets-encrypt-rate-limitlerine-takilmadan-cok-alan-adinda-ssl-san-wildcard-acme-challenge-ve-tatli-stratejiler\/\">Dodging the Wall: How I Avoid Let\u2019s Encrypt Rate Limits with SANs, Wildcards, and Calm ACME Automation<\/a>. Pair that with a fallback CA and you\u2019ll feel like you\u2019ve added shock absorbers to your issuance pipeline.<\/p>\n<p>For DNS, I\u2019ve had good luck keeping provider access scoped tightly and using per-environment credentials. If your TXT records live across multiple providers (it happens), the multi\u2011provider DNS approach with something like octoDNS helps you keep zones consistent. I walked through the operational side of that in <a href=\"https:\/\/www.dchost.com\/blog\/en\/coklu-saglayici-dns-nasil-kurulur-octodns-ile-zero%E2%80%91downtime-gecis-ve-dayaniklilik-rehberi\/\">How I Run Multi\u2011Provider DNS with octoDNS (and Sleep Through Migrations)<\/a>. Again, not required for day one, but really nice when the fleet grows.<\/p>\n<h2 id=\"section-8\"><span id=\"A_Few_Real-World_Gotchas_and_Easy_Fixes\">A Few Real-World Gotchas (and Easy Fixes)<\/span><\/h2>\n<p>I wish every setup were a smooth straight line, but there are a couple of repeat offenders I see.<\/p>\n<p>First, EAB for ZeroSSL. If you forget to add it, the account registration won\u2019t bind, and later commands will fail in ways that look like generic \u201cACME problem\u201d errors. The fix is quick: grab your EAB kid and hmac from the dashboard and re-run the registration. Do this once, and you\u2019re good.<\/p>\n<p>Second, HTTP\u201101 behind a CDN. This is the classic \u201cworks on my machine\u201d moment because the CDN or a cache tier doesn\u2019t serve the challenge file. If you must use HTTP\u201101, carve out a bypass path. But in most cases, DNS\u201101 feels like an instant level\u2011up. When you finally switch, you\u2019ll wonder why you waited.<\/p>\n<p>Third, chain surprises. Every so often, an intermediate or root update ripples through the ecosystem. Modern clients are fine, but older agents might be picky. One way I cushion that is by serving both ECDSA and RSA certificates. Modern devices get the faster ECDSA while legacy clients have a compatible RSA path. I covered that balancing act in <a href=\"https:\/\/www.dchost.com\/blog\/en\/nginx-apachede-ecdsa-rsa-ikili-ssl-uyumluluk-mu-hiz-mi-ikisini-birden-nasil-alirsin\/\">The Sweet Spot for Speed and Compatibility: Serving Dual ECDSA + RSA Certificates on Nginx and Apache<\/a>. Nothing complicated\u2014just a hedge that keeps support tickets from old browsers out of your queue.<\/p>\n<p>Fourth, forgetting the reload. I\u2019ve seen perfect renewals that never made it to production because the web server didn\u2019t reload. acme.sh\u2019s <strong>&#8211;reloadcmd<\/strong> hook is your friend. Test it independently. A fast way is to run the reload command by hand and confirm your server picks up a known change (like a quick certificate fingerprint comparison).<\/p>\n<p>Fifth, visibility. If you don\u2019t log, you won\u2019t know when a fallback saved your day. I like a single log file for the wrapper and a quick notification if we ever use the secondary CA. Not because it\u2019s an error, but because it\u2019s good to know. If fallbacks become frequent, you can investigate the root cause\u2014maybe a credential expired, maybe a DNS zone changed ownership\u2014and fix it once.<\/p>\n<h2 id=\"section-9\"><span id=\"What_About_Staging_Canaries_and_Safe_Rollouts\">What About Staging, Canaries, and Safe Rollouts?<\/span><\/h2>\n<p>One of my clients had a sprawling fleet across several regions and a small window for certificate change freezes. What made it work was a layered approach. We kept the canary domain renewing first each night. If the canary renewal happened on the primary CA, we let the rest follow on that path. If the canary fell back to ZeroSSL, we logged it and proceeded anyway because uptime beats purity. By morning, we\u2019d review the logs with coffee and decide whether to tweak anything. Most days there was nothing to do. That\u2019s the goal.<\/p>\n<p>If you like to go the extra mile, you can also verify that your certs load cleanly from a couple of vantage points after each renewal. A tiny script with curl or openssl s_client checks is usually enough. This isn\u2019t about paranoia; it\u2019s about quickly catching a path issue or a server not reloading on one node of a cluster.<\/p>\n<h2 id=\"section-10\"><span id=\"How_Fallback_Plays_with_Rate_Limits_and_Issuance_Bursts\">How Fallback Plays with Rate Limits and Issuance Bursts<\/span><\/h2>\n<p>Renewals usually tick along quietly, but issuance bursts are where folks meet rate limits. A fallback CA won\u2019t eliminate all limits, but it does spread load when you need it most. Think of it like having two checkout lines at the grocery store. If one line piles up, you drift to the other without making a scene. Combine that with pacing\u2014issue steadily over hours, not in one massive minute\u2014and you\u2019ll glide past the usual pain points. I unpacked that pacing and a few favorite tricks in <a href=\"https:\/\/www.dchost.com\/blog\/en\/lets-encrypt-rate-limitlerine-takilmadan-cok-alan-adinda-ssl-san-wildcard-acme-challenge-ve-tatli-stratejiler\/\">Dodging the Wall: How I Avoid Let\u2019s Encrypt Rate Limits with SANs, Wildcards, and Calm ACME Automation<\/a>.<\/p>\n<h2 id=\"section-11\"><span id=\"A_Note_on_Secrets_and_Safe_Storage\">A Note on Secrets and Safe Storage<\/span><\/h2>\n<p>Your DNS API tokens and EAB keys are the keys to your kingdom. Keep them in a secure place, restrict their scope, and rotate them on a sensible schedule. I like using environment files readable by only the automation user and separating production from staging credentials. If you\u2019re already invested in a secrets workflow, plug this right in\u2014no need to reinvent the wheel. The idea is simple: make the secure path the default path so you don\u2019t have to think about it later.<\/p>\n<h2 id=\"section-12\"><span id=\"ZeroSSL_vs_Lets_Encrypt_The_Narrative_That_Actually_Matters\">ZeroSSL vs Let\u2019s Encrypt: The Narrative That Actually Matters<\/span><\/h2>\n<p>People often ask, \u201cWhich should I use?\u201d Here\u2019s my honest answer: use both. Pick one as your primary based on comfort and tooling, and keep the other ready. That\u2019s the whole story. In a real environment, uptime is the KPI that matters. When one path hiccups, the other keeps your promises. The rest is trivia.<\/p>\n<h2 id=\"section-13\"><span id=\"Rolling_Back_Gracefully_If_You_Need_To\">Rolling Back Gracefully If You Need To<\/span><\/h2>\n<p>Sometimes you\u2019ll want to switch back after a fallback, not just for defaults but for the live certificate too. There\u2019s no ceremony needed. Just re\u2011issue with your preferred CA and let your install step update the files. Your web server reload picks up the latest set. If you schedule this during a low\u2011traffic window, no one will notice. You stay in control of the timing and the outcome.<\/p>\n<h2 id=\"section-14\"><span id=\"When_DNS_Is_Your_Bottleneck\">When DNS Is Your Bottleneck<\/span><\/h2>\n<p>If your DNS provider is slow to publish TXT records or rate limits API calls, that can steal time from your renewal window. A trick I like: spread renewals throughout the day with a randomized delay, and avoid doing all tenants at once. Another neat win is ensuring your DNS TTLs are not absurdly high for the TXT record names you use. You want those changes to appear quickly. If you ever graduate to a multi\u2011provider DNS setup, it\u2019s surprising how much resilience you gain. I detail that journey in <a href=\"https:\/\/www.dchost.com\/blog\/en\/coklu-saglayici-dns-nasil-kurulur-octodns-ile-zero%E2%80%91downtime-gecis-ve-dayaniklilik-rehberi\/\">How I Run Multi\u2011Provider DNS with octoDNS (and Sleep Through Migrations)<\/a>.<\/p>\n<h2 id=\"section-15\"><span id=\"Operational_Comfort_Checklists_That_Actually_Work\">Operational Comfort: Checklists That Actually Work<\/span><\/h2>\n<p>When I finish setting this up, I run a small mental checklist. First: accounts registered with both CAs? Second: DNS credentials confirmed and scoped? Third: initial issuance done and server reloading cleanly? Fourth: wrapper script tested in staging, then production? Fifth: logging somewhere I\u2019ll actually read it? Sixth: a canary I can trust? That\u2019s it. It\u2019s not fancy, but it keeps me honest.<\/p>\n<h2 id=\"section-16\"><span id=\"One_More_Quiet_Edge_Serving_Dual_Certs\">One More Quiet Edge: Serving Dual Certs<\/span><\/h2>\n<p>I mentioned it earlier, but it\u2019s worth underlining. I\u2019ve seen fallbacks work perfectly and then a corner of the user base struggle because of an older TLS stack. Dual certificates\u2014ECDSA and RSA\u2014are a subtle insurance policy. Modern clients get the speed, older clients get the compatibility, and you get fewer \u201cit works here but not there\u201d mysteries. If you want the exact Nginx and Apache snippets, the walkthrough is here: <a href=\"https:\/\/www.dchost.com\/blog\/en\/nginx-apachede-ecdsa-rsa-ikili-ssl-uyumluluk-mu-hiz-mi-ikisini-birden-nasil-alirsin\/\">The Sweet Spot for Speed and Compatibility: Serving Dual ECDSA + RSA Certificates on Nginx and Apache<\/a>.<\/p>\n<h2 id=\"section-17\"><span id=\"The_Wrap-Up_Calm_Renewals_No_Drama\">The Wrap-Up: Calm Renewals, No Drama<\/span><\/h2>\n<p>Let me leave you with the picture I wish someone had drawn for me years ago: ACME can be as calm as you make it. With acme.sh, you\u2019re not locking yourself into one CA. You\u2019re giving yourself a backup plan that requires almost no extra mental load. Register accounts with both Let\u2019s Encrypt and ZeroSSL. Choose DNS\u201101 for challenges that don\u2019t fight your infrastructure. Add a tiny wrapper that tries the primary and falls back to the secondary. Keep your reloads tight, your logs visible, and a canary renewing first. That\u2019s the recipe.<\/p>\n<p>If you want to dive deeper into the rate limit side of the story, have a look at <a href=\"https:\/\/www.dchost.com\/blog\/en\/lets-encrypt-rate-limitlerine-takilmadan-cok-alan-adinda-ssl-san-wildcard-acme-challenge-ve-tatli-stratejiler\/\">my calm strategies for avoiding Let\u2019s Encrypt rate limits<\/a>. And if DNS resilience is your next upgrade, the multi\u2011provider guide with octoDNS is a friendly companion for that, too.<\/p>\n<p>Set it up once, let it run, and enjoy the quiet. Hope this was helpful! See you in the next post\u2014and may your renewals be as uneventful as a good night\u2019s sleep.<\/p>\n<\/div>","protected":false},"excerpt":{"rendered":"<p>It was one of those quiet Sundays when everything should have been boring. Coffee in hand, inbox under control, servers humming. Then my phone buzzed: \u201cRenewal failed. Certificate expires in 2 days.\u201d My stomach did that tiny flop it always does when something simple decides to be dramatic. The culprit? A brief blip on an [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":1876,"comment_status":"","ping_status":"","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[26],"tags":[],"class_list":["post-1875","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-teknoloji"],"_links":{"self":[{"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/posts\/1875","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/comments?post=1875"}],"version-history":[{"count":0,"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/posts\/1875\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/media\/1876"}],"wp:attachment":[{"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/media?parent=1875"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/categories?post=1875"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/tags?post=1875"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}