İçindekiler
- 1 The moment CAA clicked for me
- 2 What CAA really is (and why you’ll grow to love it)
- 3 The anatomy of a CAA record: issue, issuewild, iodef (and friends)
- 4 Let’s Encrypt and ZeroSSL: getting authorization right
- 5 Getting the wildcard story straight: issue vs issuewild
- 6 iodef: the unsung hero that keeps you in the loop
- 7 A calm multi‑CA strategy that just works
- 8 Implementation patterns and the little traps
- 9 A quick word on DNSSEC and CAA trust
- 10 Wrapping it all up: CAA as your quiet policy engine
The moment CAA clicked for me
So there I was, late on a Thursday, sipping lukewarm coffee and staring at a DNS zone that absolutely refused to issue a wildcard certificate. The setup looked perfect. ACME automation was humming, DNS was clean, the validation was passing, and yet the CA kept replying with a polite “sorry, not authorized.” I remember laughing out loud in that slightly unhinged way only sysadmins and DevOps folks will recognize. It wasn’t the servers or the ACME client. It was CAA—quiet, unobtrusive, and completely in charge of who gets to mint certs for your domain. The domain had a CAA record that allowed non-wildcard issuance, but not wildcard. Subtle. Brutal. Educational.
If you’ve ever had that “why won’t my certificate issue?” moment, this is for you. We’ll unpack CAA in a friendly, real-world way. We’ll look at how Let’s Encrypt and ZeroSSL read CAA, what issue and issuewild actually do, how iodef helps you get human-readable alerts, and the strategy I use to run a multi‑CA setup without drama. I’ll share the patterns I lean on, the gotchas that still bite now and then, and the little habits that keep teams out of late-night firefights.
What CAA really is (and why you’ll grow to love it)
CAA is like the guest list at a cozy dinner party. Only the names on that list get in. In DNS terms, a CAA record says which Certificate Authorities (CAs) are allowed to issue certificates for your domain. If there’s no CAA record, any trusted CA can issue. If there is one, the rules kick in. The real win is control. You can bless Let’s Encrypt for non-wildcard, bless ZeroSSL for wildcard, and route violation notices to your inbox with iodef. You can even tell a CA exactly which account is allowed to request a cert for your domain—handy when you’ve got multiple teams or vendors touching things.
Here’s the thing: CAA isn’t flashy. You won’t see a dashboard dancing with charts. But it’s quietly mighty. It prevents accidental or unauthorized issuance, reduces surprise cert sprawl, and when you’re doing ACME at scale, it becomes your guardrail. I’ve seen organizations go from mystery certs appearing out of nowhere to a world where every certificate has a reason, a paper trail, and a predictable CA.
The anatomy of a CAA record: issue, issuewild, iodef (and friends)
Let’s break the core tags down in plain English first, then we’ll look at examples you can drop into a zone file.
issue: Authorizes certificate issuance for normal (non-wildcard) hostnames. If you say issue “letsencrypt.org”, you’re telling the world that normal certificates for your domain can be issued by Let’s Encrypt.
issuewild: Same idea, but specifically for wildcard certificates like *.example.com. This is where that Thursday-night debugging session went sideways for me. A domain can happily allow issue for Let’s Encrypt, but if you forget issuewild, your wildcard orders will fail, even if everything else looks perfect.
iodef: This is your “ping me if something weird happens” tag. You can provide an email or an HTTPS endpoint. If a CA finds something off—or wants to send you a policy violation notice—they use this contact. It’s especially useful for big teams or multi-tenant systems where you want early heads-up.
There are also optional parameters like accounturi that some CAs (notably Let’s Encrypt) support. That’s a way to say, “Even if you’re authorized, restrict issuance to this specific ACME account.” It’s an extra glove over the handshakes, and it helps when multiple automation stacks operate under the same domain.
Here are realistic examples you can adapt. These go in your zone file or DNS control panel. Your syntax will differ a bit depending on the provider, but the shape is the same.
; Allow Let’s Encrypt for normal certs
example.com. CAA 0 issue "letsencrypt.org"
; Allow ZeroSSL for normal certs as well
example.com. CAA 0 issue "zerossl.com"
; Explicitly allow wildcard issuance by Let’s Encrypt and ZeroSSL
example.com. CAA 0 issuewild "letsencrypt.org"
example.com. CAA 0 issuewild "zerossl.com"
; Deny issuance by everyone else (empty value)
; Note: This forbids issuers at this label unless explicitly allowed elsewhere the CA is required to consider
; (see discussion below about CNAME targets and parents)
example.com. CAA 0 issue ";"
example.com. CAA 0 issuewild ";"
; Get notifications over email and HTTPS (CAs may send iodef reports)
example.com. CAA 0 iodef "mailto:[email protected]"
example.com. CAA 0 iodef "https://caa.example.com/report"
; Lock issuance to a specific Let’s Encrypt account (Let’s Encrypt supports this)
example.com. CAA 0 issue "letsencrypt.org; accounturi=https://acme-v02.api.letsencrypt.org/acme/acct/12345678"
A quick story from the trenches: one of my clients had a clean wildcard policy at the apex but forgot that a subdomain pointed to a vendor via CNAME. The vendor’s domain had CAA records authorizing their preferred CA. Because the CAA check follows the CNAME target in addition to checking the queried name and its parents, the effective policy included the vendor’s allowance. Net result: a certificate issued through the vendor’s CA even though the parent zone intended to restrict it. The lesson I took away—and now repeat often—is to put explicit CAA records exactly where you care most, especially on hostnames that CNAME to third parties, and use iodef to keep your eyes on the system.
Most folks I work with authorize at least two CAs for resilience—typically Let’s Encrypt and ZeroSSL—so their ACME automation has a graceful fallback. With CAA, the “blessings” look like you’d expect:
example.com. CAA 0 issue "letsencrypt.org"
example.com. CAA 0 issue "zerossl.com"
example.com. CAA 0 issuewild "letsencrypt.org"
example.com. CAA 0 issuewild "zerossl.com"
Now add nuance. Let’s Encrypt supports an accounturi parameter, which lets you restrict issuance to a specific ACME account. This is wildly useful when multiple teams or vendors run ACME against the same domain. If you’ve ever had that nervous feeling that someone, somewhere, might be automating a cert without telling you, accounturi is how you sleep better at night.
example.com. CAA 0 issue "letsencrypt.org; accounturi=https://acme-v02.api.letsencrypt.org/acme/acct/12345678"
ZeroSSL, in my experience, behaves well with standard issue and issuewild tags using zerossl.com as the value. If you’re curious about the finer points, you can skim Let’s Encrypt’s CAA guidance and the updated CAA RFC 8659 for background, and then verify what your specific tooling and provider support in practice. A tiny test zone—and watching a couple of live ACME runs—teaches more than a dozen docs.
One approach I like for production is pairing a resilient ACME client configuration with a conservative CAA policy. The automation tries Let’s Encrypt first, then falls back to ZeroSSL when needed. The CAA allows both, but the system naturally prefers the first. If you want to see how that looks in the real world, I wrote about redundant ACME automation with acme.sh and a Let’s Encrypt → ZeroSSL fallback and how it reduces stress around rate limits and outages.
Getting the wildcard story straight: issue vs issuewild
If CAA has a “gotcha” that bites people regularly, it’s this: wildcards are their own world. A domain can happily authorize Let’s Encrypt via issue and still fail wildcard issuance because issuewild wasn’t included. And it’s the kind of problem you don’t notice until renewal day at 2 a.m.
I’ve developed a checklist for myself. First, explicitly allow the CA for non-wildcard with issue. Second, explicitly allow the CA for wildcard with issuewild. Third, decide how strict you want to be about denial by adding issue “;” and issuewild “;” after your allows, especially on zones where you don’t want surprise issuers creeping in via a CNAME target. Fourth, make sure your ACME automation actually requests wildcards correctly—DNS‑01 is your friend here—and that your DNS provider’s API is smooth enough to handle TXT updates quickly.
There’s an interesting interaction worth highlighting. When a CA evaluates CAA, it checks the requested name, walks up the parent chain, and follows any CNAME target for additional policy. That means permissions can stack up in ways you might not expect. If a subdomain CNAMEs to a third party, and that third party authorizes a CA for wildcard issuance on their target zone, the effective policy for your exact hostname could end up allowing issuance even if your apex tried to be restrictive. That isn’t a bug; it’s how the spec resolves policy from multiple places. My habit is to place explicit CAA records on any hostname that matters, not only at the apex.
iodef: the unsung hero that keeps you in the loop
iodef is a little like turning on motion-activated lights in your backyard. Most nights, nothing happens. But if something does move, you get a ping. With CAA, you can provide an email address and/or an HTTPS endpoint where CAs send notifications about policy violations or similar events. When you’re doing controlled issuance—especially in bigger orgs—those “hey, we tried to issue and couldn’t” notes can save hours of head-scratching.
I like to set both email and HTTPS. Email is timeless; it lands where people are. The HTTPS endpoint gives you a place to collect and analyze events if you want to get fancy later. My bias is to keep it simple: an internal webhook that logs to the same place as deployment events. When something goes wrong in cert land, I want it sitting next to my operational timeline, not buried in a separate system.
example.com. CAA 0 iodef "mailto:[email protected]"
example.com. CAA 0 iodef "https://caa.example.com/report"
If you want a quick reference that stays close to the practical day-to-day, Cloudflare’s practical CAA record reference does a good job walking through the formats. The rules are simple, but the context—especially once CNAMEs and subdomain overrides enter the chat—benefits from being spelled out in examples.
A calm multi‑CA strategy that just works
The simplest strategy I’ve found over the years is this: authorize two CAs you actually use, make your ACME automation prefer one and fall back to the other, and write CAA in a way that matches that plan. In most cases, that means Let’s Encrypt first, ZeroSSL second. Wildcards included. And then a soft fence around your property using issue “;” and issuewild “;” after the permissions you do want.
Why two CAs? Three reasons I see in the real world. First, rate limits. Even with careful staging, busy fleets can run hot now and then. Second, outages. Rare, but when they happen, it’s nice not to be stuck. Third, policy changes. If one CA tightens something that hits your architecture, you have breathing room while you adjust. The operational cost of authorizing two CAs in CAA is virtually zero, and it buys you a lot of calm.
When I set this up for teams, I encourage two small habits. One, put your CAA records next to the automation that depends on them. If your compose or Terraform is provisioning an ACME client, let it also declare the CAA you need. Two, test issuance deliberately from both CAs before you need it. It’s like testing backups: doing it when the house is on fire is not the mood you want.
Another note from experience: pay attention to DNS propagation and negative caching. If you change CAA and immediately kick off issuance, the CA’s resolvers might still be seeing an older view. Keep TTLs reasonable, and give yourself a short grace window before renewals. When someone tells me “it failed, then it worked an hour later,” my first suspect is propagation.
Implementation patterns and the little traps
Start at the apex, then secure the edges
Most zones should carry CAA at the apex. That covers the majority of names. But remember that subdomains can override or add to the apex policy, and CNAME targets can contribute their own CAA. If you have “special” hostnames—CDN endpoints, vendor integrations, anything that’s a linchpin—put explicit CAA there as well. It’s not much work, and it keeps surprises to a minimum.
Be explicit about wildcards
Even if you don’t use wildcards today, consider writing issuewild on day one—either authorizing the CA you plan to use or explicitly denying with an empty value. That way you won’t be guessing at two in the morning when a new team asks why their wildcard order won’t go through.
Use account-level constraints where it helps
If your organization runs multiple ACME accounts for separation of duties, use accounturi where supported to tighten who can request what. It’s not mandatory, and many healthy setups run fine without it. But when you need traceability—who requested which cert and from where—tying CAA to an account is a nice tool to have.
Remember the CNAME and parent walk
When a CA evaluates CAA, it looks at the exact hostname, then walks up the parent chain until it finds relevant records, and also follows any CNAME target to consider that domain’s CAA. The net effect can surprise folks who relied on an apex “deny” while a subdomain CNAMEs into a vendor that explicitly allows issuance for its service. This is one reason I like to keep iodef notifications on and place specific CAA on sensitive subdomains.
Propagation, TTLs, and negative answers
CAA behaves like any other DNS record when it comes to caching. If you remove an allow or add a deny, caches can hold the previous answer for a bit. When I’m changing policy near renewal, I’ll lower TTLs ahead of time and switch policy during a quiet window. It’s not about fear; it’s just avoiding unnecessary flakiness during renewals.
Testing without drama
My favorite quick test is good old dig:
dig +short CAA example.com
; For a specific host
dig +short CAA app.example.com
Then I’ll do a dry‑run issuance with my ACME client against a staging endpoint to confirm behavior. Watching the logs while the CA queries your DNS is oddly satisfying. You see exactly where it checks, what it finds, and why it proceeds or refuses.
If you ever want a second opinion on the record formats and semantics, two pieces worth bookmarking are Let’s Encrypt’s CAA guidance and RFC 8659. They’re concise enough that you can dip in, confirm a detail, and pop back out without losing your afternoon.
A quick word on DNSSEC and CAA trust
You don’t need DNSSEC for CAA to work, but pairing them is like putting good locks on a sturdy door. When your DNS is signed and the world can validate those signatures, you reduce the risk of someone spoofing answers to a CA. In practice, I’ve seen teams roll out CAA first and add DNSSEC once the DNS tooling and processes are comfortable. The two complement each other nicely. If you’re doing higher‑sensitivity workloads, it’s a natural part of the journey.
Wrapping it all up: CAA as your quiet policy engine
Let me circle back to that Thursday night. The fix turned out to be two lines of CAA for issuewild, a couple of iodef contacts, and a small note in the runbook. It wasn’t heroic. But it turned a brittle corner of the system into something calm and predictable. That’s the best outcome in this craft.
If you’re just getting started, begin with the simple shape: authorize Let’s Encrypt and ZeroSSL for both normal and wildcard issuance, add iodef to keep yourself in the loop, and consider a gentle deny to discourage unexpected issuers. If you’re running at scale, layer on account-level constraints where helpful, test both CAs in your automation, and keep an eye on CNAMEs that point to vendors. None of it is complicated; it’s just a handful of careful choices you make once and then enjoy for months.
Hope this was helpful. If you want a practical tour of failover in action, I’ve shared how I run redundant ACME automation with acme.sh and a Let’s Encrypt → ZeroSSL fallback. Here’s to fewer late nights, predictable renewals, and certificates that just show up when you need them. See you in the next post.
