Part 19 — How I Run My Entire Digital Life on a Raspberry Pi: Automate Your Wildcard Certificates Forever with a Free CNAME Trick
Table of Contents
How I killed the quarterly certbot ritual for good: acme.sh plus one permanent CNAME record pointing at acme-dns gets you fully automated wildcard Let’s Encrypt certificates on Namecheap — or literally any DNS provider — with no paid API, no static IP, and no human in the loop. Running happily on the same Raspberry Pi that hosts everything else.
The quarterly cert renewal ritual, receiving the send-off it deserves — image generated via Nano Banana
Every roughly 85 days, the same calendar reminder. SSH into the Pi. Run certbot in manual mode. Squint at two DNS challenge strings. Log into the Namecheap dashboard. Copy-paste them into TXT records. Wait for propagation while refreshing dig like a maniac. Press Enter. Pray.
Back in Part 4 of my self-hosting series, when I compared remote access options, I literally listed this as a con of the nginx route: “handling renewals with Let’s Encrypt every three months.” Then in Part 16, we went all-in on that route anyway — a battle-tested NGINX reverse proxy with no external gatekeepers, wildcard certificate and all. Total control, pure speed… and a recurring chore I kept postponing until the expiry warnings started rolling in.
Everyone will tell you the fix is obvious: “just use a certbot DNS plugin, lah.” And they’re right — if your DNS provider plays along. Here’s what almost nobody talks about: Namecheap doesn’t. There is no supported certbot DNS plugin for it, and Namecheap’s DNS API — the thing any automation would need — is gated behind requirements that read like a bad joke for a homelabber:
- You need a $50+ account balance or 20+ registered domains just to get API access.
- You must whitelist the caller’s IP address — useless when your home IP is dynamic.
- Their
setHostsAPI call replaces ALL DNS records at once, so one buggy script can nuke your MX records into orbit.
So: wildcard cert (which requires DNS validation — more on that in a second), a registrar with no free API, and a dynamic home IP. Sounds like checkmate. It isn’t. The escape hatch is a single, gloriously simple DNS trick — and once you see it, you’ll wonder why anyone still does this manually.
The architecture: delegating the challenge, not the domain
Before diving into the commands, it helps to understand what we are building and why it works with any registrar on the planet.
To issue a certificate, Let’s Encrypt must verify you actually control the domain. For wildcard certificates (*.yourdomain.tld), the only accepted proof is the DNS-01 challenge: the CA hands you a random token, and you must publish it as a TXT record at _acme-challenge.yourdomain.tld. Automating this normally means giving your ACME client API access to your DNS provider — which is exactly the door Namecheap keeps locked.
But here’s the loophole, and it’s a standard, intended part of how DNS-01 works: the validation follows CNAME records. If _acme-challenge.yourdomain.tld is a CNAME pointing somewhere else, Let’s Encrypt happily walks the pointer and reads the TXT record at the destination.
So we simply delegate that one hostname — and only that one — to a DNS server we can update programmatically: acme-dns. It’s a wonderfully minimalist DNS server with exactly one job in life: serving ACME challenge TXT records, updatable through a dead-simple REST API secured by per-account credentials. No IP whitelisting. No account balance requirements. And there’s a free public instance at auth.acme-dns.io that acme.sh talks to out of the box.
Your Pi (any IP, changes daily? don't care)
│
└──► auth.acme-dns.io REST API (username + password, no IP whitelist)
│
└──► publishes TXT at <your-uuid>.auth.acme-dns.io
▲
│ CNAME (permanent, set ONCE, by hand)
_acme-challenge.yourdomain.tld
Let's Encrypt follows the CNAME → finds the TXT → validates ✅
Your A record, your MX, your Namecheap dynamic-DNS client — all untouched. We’re delegating a single throwaway hostname whose only purpose is validation. This is why the trick is registrar-agnostic: every DNS provider on Earth lets you create a CNAME record, and that’s the only thing we ever ask of Namecheap. One manual record, once, forever.
One honest security note, because I don’t hand-wave these: whoever holds your acme-dns credentials and benefits from that CNAME can pass DNS-01 challenges for your domain — i.e., obtain certificates for it. The credentials live in a config file on your Pi (which, if you followed Part 1, sits on a LUKS-encrypted disk anyway). The paranoid can self-host acme-dns instead of using the public instance — the trick works identically.
DNS-01 validation happily follows the detour — the locked gate never even notices — image generated via Nano Banana
Step 1 — Install acme.sh
Why acme.sh instead of certbot? It’s a pure POSIX shell script — no Python stack, no compiled binaries, runs on anything from a beefy server to a Pi Zero — and its dns_acmedns integration handles the entire acme-dns dance natively, including registering an account for you. Certbot can do acme-dns too, but only via a third-party hook script; acme.sh has it built in.
$ sudo apt install curl cron openssl -y
$ curl https://get.acme.sh | sh -s email=admin@yourdomain.tld
$ source ~/.bashrc
$ acme.sh --set-default-ca --server letsencrypt
That last line matters: acme.sh defaults to ZeroSSL, and we explicitly want Let’s Encrypt.
Step 2 — Kick off the issuance (and let it pause)
$ ACMEDNS_UPDATE_URL="https://auth.acme-dns.io/update" \
acme.sh --issue \
--dns dns_acmedns \
-d yourdomain.tld \
-d '*.yourdomain.tld' \
--server letsencrypt
On the very first run, acme.sh silently registers a fresh account with acme-dns, receives credentials plus a unique subdomain, stores everything in ~/.acme.sh/yourdomain.tld_ecc/yourdomain.tld.conf — and then does something delightfully civilized. It prints the exact CNAME you need and waits for you:
##########################################################
# Create _acme-challenge.yourdomain.tld CNAME <uuid>.auth.acme-dns.io DNS entry #
##########################################################
Press enter to continue...
Do not press Enter yet. Good. Progress.
Step 3 — The one manual step you’ll ever do
Head to the Namecheap dashboard → your domain → Advanced DNS, and add a new CNAME record _acme-challenge with a value of <uuid>.auth.acme-dns.io.
Verify propagation from a second terminal before touching anything:
$ dig CNAME _acme-challenge.yourdomain.tld +short
// should print: <uuid>.auth.acme-dns.io.
This record is permanent. You will never edit it again. Frame it if you like.
Step 4 — Press Enter and watch the magic
Back in the acme.sh terminal, press Enter. It pushes the challenge token to acme-dns via the REST API, Let’s Encrypt follows your shiny new CNAME, finds the TXT record, and:
[...] Your cert is in: /home/USER/.acme.sh/yourdomain.tld_ecc/yourdomain.tld.cer
[...] Your cert key is in: /home/USER/.acme.sh/yourdomain.tld_ecc/yourdomain.tld.key
[...] And the full-chain cert is in: /home/USER/.acme.sh/yourdomain.tld_ecc/fullchain.cer
[...] Next renewal time picked from ARI window: ...
Notice that last line: acme.sh doesn’t just count down 60 days like it’s 2019. It asks Let’s Encrypt directly for the optimal renewal window via ACME Renewal Information (RFC 9773) — so if the CA ever needs to mass-revoke or shift load, your Pi reacts automatically. Nice touch for a shell script, ah?
Step 5 — Feed the certs to the NGINX from Part 16
If you built your reverse proxy following Part 16, your nginx container mounts a certificate directory and every server block includes the shared SSL config. The only thing that changes is where the files live and what they’re called — acme.sh does not use certbot’s /etc/letsencrypt at all:
acme.sh vs. certbot
Point the volume mount of your Part 16 nginx container at the new home and adjust the filenames in your SSL config accordingly:
volumes:
- /home/USER/.acme.sh/yourdomain.tld_ecc:/etc/ssl/yourdomain.tld:ro
Everything else from Part 16 — the hardened TLS settings, the per-service reverse-proxy configs, the mTLS provisioning — carries over untouched.
Step 6 — Confirm the cron job and walk away
acme.sh installs its own cron entry during setup. Verify:
$ crontab -l | grep acme
You should see a daily invocation of acme.sh --cron. It checks every certificate against its ARI window and renews only when actually due — completely unattended, since the CNAME never changes and acme-dns needs no human. Leave the job in the user crontab, by the way: all credentials and certs are owned by your user, and running it as root just invites permission-salad you’ll debug six months later. If it’s somehow missing, acme.sh --install-cronjob puts it back.
Renewal day at my place, from now until the heat death of the universe — image generated via Nano Banana
Lessons learned the hard way (so you don’t have to)
War stories from my first run, condensed:
- Don’t pre-register with acme-dns manually via
curl. Every tutorial’s first instinct, including mine. acme.sh registers its own account on first run and uses those credentials — your hand-crafted registration becomes an orphan, and worse, you’ll put the wrong CNAME into DNS. Let acme.sh drive; use the CNAME it prints. - Don’t create
~/.acme.sh/acmedns.json. That JSON credentials file is the certbot hook format. acme.sh ignores it completely and stores everything in the per-domain.confinstead. I created one, chased a ghost for twenty minutes, deleted it. - Re-running after a hiccup? Add
--force. acme.sh remembers a completed validation and will skip straight past DNS-01 otherwise — great for idempotency, confusing when you’re debugging. - 90 days is a feature, not a bug. Short lifetimes limit the damage of a leaked key — and Let’s Encrypt is moving toward even shorter ones. The entire point of today’s exercise is that the lifetime becomes irrelevant once renewal is a machine’s problem instead of yours.
Wrapping up
One CNAME record. That’s the entire toll for escaping the quarterly ritual — no paid API tier, no static IP, no third-party tunnel provider holding your traffic hostage, no exceptions carved out of your privacy stack. The Pi requests, validates, renews, and serves its own wildcard certificate for every subdomain behind the Part 16 NGINX, forever, on the same <15W it always sips.
And since the trick lives entirely in DNS-standard behavior, it doesn’t care who your registrar is. Namecheap today, anyone else tomorrow — the CNAME comes with you. If you’re just joining, the whole journey from bare hardware to this point lives in the series master index. Now if you’ll excuse me, I have a recurring calendar reminder to delete.