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: “Renewal failed. Certificate expires in 2 days.” 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’d never rely on a single certificate authority again, no matter how reliable they usually are.
If you’ve ever had that oh-no-not-SSL-again moment, this guide is my friendly nudge toward a calmer setup: using acme.sh with a clean, pragmatic fallback from Let’s Encrypt to ZeroSSL. I’ll 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’ll 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.
İçindekiler
- 1 Why Redundancy for ACME Actually Matters
- 2 Meet acme.sh and the Idea of CA Fallback
- 3 Validation That Doesn’t Bite: Why DNS‑01 Quietly Wins
- 4 The Plan: A Calm Fallback Workflow
- 5 Step-by-Step: acme.sh with Let’s Encrypt First, ZeroSSL as Fallback
- 6 Testing Without Tears
- 7 Scaling the Pattern for Many Domains or Tenants
- 8 A Few Real-World Gotchas (and Easy Fixes)
- 9 What About Staging, Canaries, and Safe Rollouts?
- 10 How Fallback Plays with Rate Limits and Issuance Bursts
- 11 A Note on Secrets and Safe Storage
- 12 ZeroSSL vs Let’s Encrypt: The Narrative That Actually Matters
- 13 Rolling Back Gracefully If You Need To
- 14 When DNS Is Your Bottleneck
- 15 Operational Comfort: Checklists That Actually Work
- 16 One More Quiet Edge: Serving Dual Certs
- 17 The Wrap-Up: Calm Renewals, No Drama
Why Redundancy for ACME Actually Matters
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’re changing tires on a moving car. Let’s 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’s DNS took an extra minute to propagate. Or maybe a regional network glitch briefly blocks an endpoint. It doesn’t have to be dramatic to be disruptive.
Here’s the thing: ACME automation is only as reliable as the weakest part of your chain. If your validation relies on a single provider, a single DNS API, or a single CA, a tiny snag can stall renewals. Redundancy isn’t about being paranoid; it’s about being practical. You don’t have to overhaul your whole workflow—just add one extra lane. If Let’s Encrypt stumbles, ZeroSSL steps in. Renewals continue, your web servers reload, and your Sunday stays boring in the best possible way.
And if you’re 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’t plan for. Building in fallback gives you predictable outcomes. If you’re curious how DNS challenges scale in those environments, I shared a detailed story in Bring Your Own Domain, Get Auto‑SSL: How DNS‑01 ACME Scales Multi‑Tenant SaaS Without Drama.
Meet acme.sh and the Idea of CA Fallback
If you’ve used certbot or another ACME client, acme.sh will feel refreshingly straightforward. It’s a shell script that speaks ACME fluently and integrates with a long list of DNS providers for DNS‑01 challenges. The magic feature for our story is that acme.sh supports multiple certificate authorities and lets you switch between them with a single flag. You can register accounts with Let’s Encrypt and ZeroSSL, choose one as your default, and then purposely fail over to the other when needed. Simple ingredients, powerful result.
In my experience, the best approach is to make Let’s 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—the point is you have two. When one path has a pebble in the road, you keep moving by taking the other path.
If you want to glance at the project straight from the source, the acme.sh documentation and repository is a helpful companion as you follow along here.
Validation That Doesn’t Bite: Why DNS‑01 Quietly Wins
Let’s talk validation, because it’s the part that bites when you least expect it. HTTP‑01 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’t see it. DNS‑01 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.
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’s boring in the best way. And if you’re running a multi-tenant setup, DNS‑01 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 Bring Your Own Domain, Get Auto‑SSL: How DNS‑01 ACME Scales Multi‑Tenant SaaS Without Drama.
One more subtle point: if you rely on TXT records for challenges, the reliability of your DNS matters. I’m a fan of a multi‑provider DNS setup so you’re not betting renewals on a single vendor. If that sounds interesting, I shared how I do it in How I Run Multi‑Provider DNS with octoDNS (and Sleep Through Migrations). You don’t have to go that far on day one, but it’s a nice move when you’re aiming for “set it and forget it” reliability.
The Plan: A Calm Fallback Workflow
Here’s the workflow that has served me and my clients well:
First, create ACME accounts with both Let’s 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‑01 with a provider API that’s stable for you. Third, issue your certificate with Let’s Encrypt like you normally would. Fourth, add a tiny wrapper script around acme.sh. That script tries to renew with Let’s Encrypt, and if it fails—because of a transient issue, a rate limit, or anything else—it switches to ZeroSSL, renews, reloads your web server, and logs the failover so you know it happened. That’s it. Nothing fancy, just a second brake pedal when the first gets soft.
A little tip I learned the hard way: always test your flow in a staging environment so you don’t accidentally trigger rate limits while experimenting. The Let’s Encrypt staging environment 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.
Step-by-Step: acme.sh with Let’s Encrypt First, ZeroSSL as Fallback
1) Install acme.sh
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.
curl https://get.acme.sh | sh -s [email protected]
# or update an existing install
~/.acme.sh/acme.sh --upgrade --auto-upgrade
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.
2) Register ACME accounts with both CAs
Start with Let’s Encrypt:
acme.sh --register-account -m [email protected] --server letsencrypt
Then register with ZeroSSL. You’ll need EAB credentials from your ZeroSSL account dashboard. If you haven’t grabbed them before, ZeroSSL explains how to do that here: ZeroSSL EAB guide. Once you have the kid and hmac key, run:
acme.sh --register-account -m [email protected]
--server zerossl
--eab-kid "YOUR_EAB_KID"
--eab-hmac-key "YOUR_EAB_HMAC"
Now you’re ready to talk to both CAs. You can set Let’s Encrypt as default for new orders:
acme.sh --set-default-ca --server letsencrypt
3) Configure DNS‑01 credentials
Pick your DNS provider plugin. As an example, Cloudflare is “dns_cf” and uses an API token:
export CF_Token="your-cloudflare-token"
export CF_Account_ID="optional-if-needed"
export CF_Zone_ID="optional-if-needed"
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.
4) Issue your first certificate with Let’s Encrypt
Let’s say we want both the apex and wildcard so everything under your domain is covered. That’s a nice fit for DNS‑01:
# RSA certificate (default)
acme.sh --issue
--server letsencrypt
--dns dns_cf
-d example.com -d *.example.com
Optionally, issue an ECDSA cert for modern clients, while keeping RSA for the long tail. It’s a trick I like because it balances speed with compatibility. If that’s new to you, I wrote about serving both cleanly in The Sweet Spot for Speed and Compatibility: Serving Dual ECDSA + RSA Certificates on Nginx and Apache.
# ECDSA certificate (parallel to RSA)
acme.sh --issue
--server letsencrypt
--dns dns_cf
-d example.com -d *.example.com
-k ec-256
Install the certificate to paths your web server expects, and add a reload hook so renewals take effect automatically:
# RSA install
acme.sh --install-cert -d example.com
--key-file /etc/ssl/example.com/rsa.key
--fullchain-file /etc/ssl/example.com/rsa.fullchain.pem
--reloadcmd "systemctl reload nginx"
# ECDSA install
acme.sh --install-cert -d example.com --ecc
--key-file /etc/ssl/example.com/ecdsa.key
--fullchain-file /etc/ssl/example.com/ecdsa.fullchain.pem
--reloadcmd "systemctl reload nginx"
From here, acme.sh can renew without extra arguments. But we want a safety net.
5) Add a tiny CA‑fallback wrapper
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’s 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’s Encrypt for the next cycle. Simple and easy to reason about.
#!/usr/bin/env bash
set -euo pipefail
DOMAINS=(
"example.com"
)
# Source DNS credentials and any acme.sh env
source /etc/acme.d/env.sh
ACME=~/.acme.sh/acme.sh
LOG=/var/log/acme-fallback.log
TS() { date -u +"%Y-%m-%dT%H:%M:%SZ"; }
fallback_issue() {
local d=$1
echo "$(TS) [WARN] LE renew failed for $d, trying ZeroSSL..." | tee -a "$LOG"
$ACME --set-default-ca --server zerossl
# RSA
$ACME --issue -d "$d" -d "*.${d}" --dns dns_cf --force || return 1
$ACME --install-cert -d "$d"
--key-file /etc/ssl/${d}/rsa.key
--fullchain-file /etc/ssl/${d}/rsa.fullchain.pem
--reloadcmd "systemctl reload nginx"
# ECDSA (if you use dual certs)
$ACME --issue -d "$d" -d "*.${d}" --dns dns_cf -k ec-256 --force || return 1
$ACME --install-cert -d "$d" --ecc
--key-file /etc/ssl/${d}/ecdsa.key
--fullchain-file /etc/ssl/${d}/ecdsa.fullchain.pem
--reloadcmd "systemctl reload nginx"
echo "$(TS) [INFO] ZeroSSL fallback succeeded for $d" | tee -a "$LOG"
$ACME --set-default-ca --server letsencrypt
}
try_renew() {
local d=$1
echo "$(TS) [INFO] Attempting LE renew for $d" | tee -a "$LOG"
set +e
$ACME --set-default-ca --server letsencrypt
$ACME --renew -d "$d"
local rc=$?
set -e
if [[ $rc -ne 0 ]]; then
fallback_issue "$d" || { echo "$(TS) [ERROR] Fallback failed for $d" | tee -a "$LOG"; return 1; }
else
echo "$(TS) [INFO] LE renew OK for $d" | tee -a "$LOG"
fi
}
for d in "${DOMAINS[@]}"; do
try_renew "$d"
done
A couple of notes. First, I’m re‑issuing on fallback with –force 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’s 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’t matter.
6) Run it on a timer, not a hope
acme.sh installs its own cron by default, and that’s 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’t stampede at the same minute. Even a small jitter makes your life easier when you scale.
# /etc/systemd/system/acme-fallback.service
[Unit]
Description=ACME renew with fallback
[Service]
Type=oneshot
User=acme
Group=acme
ExecStart=/usr/local/sbin/acme-fallback.sh
# /etc/systemd/system/acme-fallback.timer
[Unit]
Description=Daily ACME renew with fallback (jittered)
[Timer]
OnCalendar=daily
RandomizedDelaySec=1800
Persistent=true
[Install]
WantedBy=timers.target
Enable and start the timer, and you’re off:
systemctl daemon-reload
systemctl enable --now acme-fallback.timer
Testing Without Tears
Before you trust any automation, make it prove itself. I keep a “canary” domain whose only job is to renew first. If the canary renews, I’m confident the rest will follow. During setup, point your script at the Let’s Encrypt staging environment for a dry run, then flip to production when you’re happy. A canary catches obvious issues: missing DNS credentials, a reload hook that points to the wrong service name, or a file path that’s off by one directory.
acme.sh also gives you a quick snapshot of what’s due for renewal. It’s not fancy, but it’s helpful when you’re spot-checking:
acme.sh --list
When I’m rolling this out for a client, I’ll 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.
Scaling the Pattern for Many Domains or Tenants
Once you’ve got one domain happily renewing with fallback, expanding isn’t hard. The domain list in your wrapper script can be driven by a file or pulled from an internal service. The logic doesn’t change. If you run a multi-tenant platform, you’ll eventually think about how many domains go into a single certificate. Combining related hostnames can be neat, but there’s 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.
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 Dodging the Wall: How I Avoid Let’s Encrypt Rate Limits with SANs, Wildcards, and Calm ACME Automation. Pair that with a fallback CA and you’ll feel like you’ve added shock absorbers to your issuance pipeline.
For DNS, I’ve 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‑provider DNS approach with something like octoDNS helps you keep zones consistent. I walked through the operational side of that in How I Run Multi‑Provider DNS with octoDNS (and Sleep Through Migrations). Again, not required for day one, but really nice when the fleet grows.
A Few Real-World Gotchas (and Easy Fixes)
I wish every setup were a smooth straight line, but there are a couple of repeat offenders I see.
First, EAB for ZeroSSL. If you forget to add it, the account registration won’t bind, and later commands will fail in ways that look like generic “ACME problem” errors. The fix is quick: grab your EAB kid and hmac from the dashboard and re-run the registration. Do this once, and you’re good.
Second, HTTP‑01 behind a CDN. This is the classic “works on my machine” moment because the CDN or a cache tier doesn’t serve the challenge file. If you must use HTTP‑01, carve out a bypass path. But in most cases, DNS‑01 feels like an instant level‑up. When you finally switch, you’ll wonder why you waited.
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 The Sweet Spot for Speed and Compatibility: Serving Dual ECDSA + RSA Certificates on Nginx and Apache. Nothing complicated—just a hedge that keeps support tickets from old browsers out of your queue.
Fourth, forgetting the reload. I’ve seen perfect renewals that never made it to production because the web server didn’t reload. acme.sh’s –reloadcmd 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).
Fifth, visibility. If you don’t log, you won’t 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’s an error, but because it’s good to know. If fallbacks become frequent, you can investigate the root cause—maybe a credential expired, maybe a DNS zone changed ownership—and fix it once.
What About Staging, Canaries, and Safe Rollouts?
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’d review the logs with coffee and decide whether to tweak anything. Most days there was nothing to do. That’s the goal.
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’t about paranoia; it’s about quickly catching a path issue or a server not reloading on one node of a cluster.
How Fallback Plays with Rate Limits and Issuance Bursts
Renewals usually tick along quietly, but issuance bursts are where folks meet rate limits. A fallback CA won’t 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—issue steadily over hours, not in one massive minute—and you’ll glide past the usual pain points. I unpacked that pacing and a few favorite tricks in Dodging the Wall: How I Avoid Let’s Encrypt Rate Limits with SANs, Wildcards, and Calm ACME Automation.
A Note on Secrets and Safe Storage
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’re already invested in a secrets workflow, plug this right in—no need to reinvent the wheel. The idea is simple: make the secure path the default path so you don’t have to think about it later.
ZeroSSL vs Let’s Encrypt: The Narrative That Actually Matters
People often ask, “Which should I use?” Here’s my honest answer: use both. Pick one as your primary based on comfort and tooling, and keep the other ready. That’s 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.
Rolling Back Gracefully If You Need To
Sometimes you’ll want to switch back after a fallback, not just for defaults but for the live certificate too. There’s no ceremony needed. Just re‑issue 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‑traffic window, no one will notice. You stay in control of the timing and the outcome.
When DNS Is Your Bottleneck
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‑provider DNS setup, it’s surprising how much resilience you gain. I detail that journey in How I Run Multi‑Provider DNS with octoDNS (and Sleep Through Migrations).
Operational Comfort: Checklists That Actually Work
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’ll actually read it? Sixth: a canary I can trust? That’s it. It’s not fancy, but it keeps me honest.
One More Quiet Edge: Serving Dual Certs
I mentioned it earlier, but it’s worth underlining. I’ve seen fallbacks work perfectly and then a corner of the user base struggle because of an older TLS stack. Dual certificates—ECDSA and RSA—are a subtle insurance policy. Modern clients get the speed, older clients get the compatibility, and you get fewer “it works here but not there” mysteries. If you want the exact Nginx and Apache snippets, the walkthrough is here: The Sweet Spot for Speed and Compatibility: Serving Dual ECDSA + RSA Certificates on Nginx and Apache.
The Wrap-Up: Calm Renewals, No Drama
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’re not locking yourself into one CA. You’re giving yourself a backup plan that requires almost no extra mental load. Register accounts with both Let’s Encrypt and ZeroSSL. Choose DNS‑01 for challenges that don’t 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’s the recipe.
If you want to dive deeper into the rate limit side of the story, have a look at my calm strategies for avoiding Let’s Encrypt rate limits. And if DNS resilience is your next upgrade, the multi‑provider guide with octoDNS is a friendly companion for that, too.
Set it up once, let it run, and enjoy the quiet. Hope this was helpful! See you in the next post—and may your renewals be as uneventful as a good night’s sleep.
