Diagnostic TLS Certificates / CAA

CAA lookup fails due to broken DNSSEC chain

A certificate renewal fails with a vague DNS problem looking up CAA error, but your CAA record is fine when you check it yourself. The real problem is one step earlier: your domain has DNSSEC turned on, and the chain of trust behind it is broken, so a validating resolver cannot confirm any answer at all, CAA included. Here is how to prove that, why it happens, and how to fix the chain instead of chasing a CAA record that was never wrong.

dig and DNSViz Python and Node.js Registrar action required
Padlock on laptop with light trails
Photo by FlyD on Unsplash
The short answer

Under the CA/Browser Forum Baseline Requirements, a certificate authority must validate DNSSEC when it is present, and any DNSSEC validation error, which shows up as SERVFAIL, must be treated as an inability to confirm CAA policy. So the CA refuses to issue or renew, even though your actual CAA record may be perfectly fine. The usual cause is a DS record at the registrar that no longer matches the DNSKEY signing the zone at your DNS host, often left over from a half finished key rollover or a DNS migration. Check with dig @1.1.1.1 CAA example.com: SERVFAIL there, but NOERROR with dig @1.1.1.1 +cd CAA example.com, confirms DNSSEC is the cause. The fix is to make the DS record match the live DNSKEY, or to remove DNSSEC cleanly in the right order. Full commands and a script are below.

The problem in plain words

DNSSEC adds a signature to DNS answers so a resolver can prove the answer was not tampered with along the way. That proof depends on a chain: the registrar publishes a DS record, and that DS record must match a DNSKEY that your DNS host is actively using to sign the zone. If those two pieces stop matching, a validating resolver cannot build a complete, trusted chain from the root down to your domain.

When that happens, the resolver does not just ignore DNSSEC and answer anyway. It refuses to answer at all, for any record type, and returns SERVFAIL. That includes the CAA record a certificate authority checks before it issues or renews. The CA sees a broken lookup, not a real answer, and per the rules it must treat that as if it could not confirm CAA policy. It has to refuse, even though nothing about your CAA record itself is wrong.

CA asks for CAA via a validating resolver Check DS vs DNSKEY registrar vs DNS host no match Chain fails status: SERVFAIL CA refuses no cert issued
The CAA record itself is never checked. The DS-to-DNSKEY mismatch breaks the DNSSEC chain first, so the whole lookup returns SERVFAIL and the CA is required to refuse.

Why it happens

Per the CA/Browser Forum's Ballot SC-085v2, this is not a gray area for the CA. When DNSSEC is present on a domain, the CA must validate it as part of the CAA and domain control checks, and a validation error must be treated as an inability to confirm CAA policy, which means refuse. The CA has no way to tell "the chain is broken" apart from "the record you actually want is blocked," so it always assumes the safer answer.

The key insight

There is no CAA record to fix here. The CAA record can be completely correct, or even absent, and the renewal still fails, because the resolver never gets far enough to read it. The fix always happens one layer down, in the DS record at the registrar and the DNSKEY at the DNS host, not in the CAA record itself.

The fix, as a flow

Reproduce what the CA sees with a validating resolver, confirm DNSSEC is the cause by comparing a plain query against one with validation disabled, then look at the DS and DNSKEY pieces directly to see which one is stale, and fix that piece at its source, either the registrar or the DNS host.

Reproduce SERVFAIL dig @1.1.1.1 CAA Confirm with +cd succeeds means DNSSEC Compare DS vs DNSKEY registrar vs DNS host Digests now match? yes no, update DS at registrar Chain verified CAA resolves, retry renewal
Once the DS record at the registrar matches the live DNSKEY, or DNSSEC is removed in the correct order, the chain validates and the CAA lookup returns a real answer again.

How to fix it

1

Reproduce what the CA sees

Query CAA against a DNSSEC-validating resolver, the same kind of resolver a CA uses. A broken chain shows status: SERVFAIL instead of status: NOERROR.

Terminal
reproduce-servfail (shell)
dig @1.1.1.1 CAA example.com

# a bad result shows this near the top of the answer:
# ;; ->>HEADER<<- opcode: QUERY, status: SERVFAIL, id: 12345
2

Confirm it is DNSSEC, not a dead nameserver

Run the same query with +cd, which tells the resolver to skip DNSSEC validation. If this succeeds while the first query SERVFAILs, DNSSEC is the culprit, not a broken nameserver or a network problem.

Terminal
confirm-dnssec (shell)
dig @1.1.1.1 +cd CAA example.com

# a good result here, with status: NOERROR, while the plain query above
# SERVFAILs, confirms DNSSEC validation is the cause
3

Check the DS record published at the registrar

The DS record lives at the parent zone, published through your registrar. Query it directly to see the digest the registrar is advertising to the world.

Terminal
check-ds (shell)
dig DS example.com +short

# example of a live DS record from the registrar:
# 2371 13 2 A1B2C3D4E5F6...(digest continues)
4

Check the DNSKEY actually signing the zone

Query the DNSKEY at your authoritative nameserver to see the key or keys currently in use, and check the RRSIG validity window in case a signature simply expired rather than a key changing.

Terminal
check-dnskey (shell)
dig DNSKEY example.com +short

dig +dnssec DNSKEY example.com
# look at the RRSIG line for this record set and compare its
# validity window (the two timestamps) against the current date
5

Compare the digest, or use DNSViz

Compare the DS digest from step 3 against a hash of the current DNSKEY from step 4, or use DNSViz, which draws the whole chain and marks the broken link in red.

Terminal
dnsviz (shell)
# open in a browser, replacing example.com with your domain:
# https://dnsviz.net/d/example.com/dnssec/
#
# a bad result shows a red error node on the DS-to-DNSKEY link,
# or a red node flagging an expired RRSIG
6

Fix it: match the DS to the live DNSKEY, or remove DNSSEC cleanly

There is no CAA record to edit here. Option one, keep DNSSEC: get the current KSK's DS data from the DNS host, delete the stale DS record at the registrar, and add the new one exactly as shown, then wait for TTL propagation. Option two, disable DNSSEC: remove the DS record at the registrar first, wait at least one and a half times the DS TTL, then remove the DNSKEY at the DNS host. Never do both steps at once or in the wrong order, since removing the key before the DS expires is exactly what causes this SERVFAIL.

DNS record
registrar DS record
; Cloudflare shows this under DNS > Settings > DNSSEC on the zone
; paste it into your registrar's DNSSEC / DS records page exactly as shown
example.com. IN DS 2371 13 2 A1B2C3D4E5F6789...(full digest from the host)

; Option 1: replace the stale DS with this current one, then wait for
; TTL propagation, roughly 1 to 48 hours depending on the TLD

; Option 2: to disable DNSSEC safely, delete the DS record above first,
; wait at least 1.5x its TTL (about 36 hours for a 24 hour TTL), and only
; then remove the DNSKEY / turn off signing at the DNS host

How to check it worked

Re-run the CAA query against more than one validating resolver and confirm a real answer comes back, then check DNSViz for a fully green chain, then retry the actual certificate renewal.

Terminal
verify (shell)
dig @1.1.1.1 CAA example.com
dig @8.8.8.8 CAA example.com
# good result: status: NOERROR on both, with the ad flag set if DNSSEC
# is still enabled, and either the CAA records or an empty answer

# then re-open https://dnsviz.net/d/example.com/dnssec/
# good result: a fully green chain from the root down, no red or
# yellow nodes on DS, DNSKEY, or RRSIG

certbot renew
# good result: completes with no "DNS problem: SERVFAIL looking up CAA" error

The full code

Here is a script that queries CAA against a DNSSEC-validating resolver, retries the same query with checking disabled, and uses a pure function to decide whether the failure is a broken DNSSEC chain, an expired signature, or something unrelated to DNSSEC entirely. If you are on Cloudflare and the fix is to disable DNSSEC, it can also read or update the zone's DNSSEC status through the Cloudflare API. Updating the DS record itself always stays a registrar action, outside any DNS provider's record API.

View this code on GitHub Full runnable folder with tests in the dns-fixes repo.

caa_lookup_servfail_dnssec.py
"""Detect a broken DNSSEC chain that turns a CAA lookup into a SERVFAIL and,
optionally, check or change a Cloudflare zone's DNSSEC status. Safe to run
on a schedule. Stays in dry run until DRY_RUN=false.
"""
import os
import logging

logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
log = logging.getLogger("caa_lookup_servfail_dnssec")


def diagnose_caa_dnssec_break(servfail_with_validation, ok_with_cd, ds_matches_dnskey, rrsig_expired):
    """Pure decision function. No DNS I/O, no network calls.

    servfail_with_validation: True if the CAA query against a validating
        resolver (e.g. dig @1.1.1.1 CAA example.com) returned SERVFAIL.
    ok_with_cd: True if the same query with checking disabled
        (dig @1.1.1.1 +cd CAA example.com) returned NOERROR.
    ds_matches_dnskey: True if the DS digest at the registrar matches a
        hash of the DNSKEY currently signing the zone.
    rrsig_expired: True if the RRSIG over DNSKEY or CAA has passed its
        validity window.

    Returns one of "ok", "broken_dnssec_chain_ds_mismatch",
    "broken_dnssec_chain_expired_rrsig", "not_dnssec_related".
    """
    if not servfail_with_validation:
        return "ok"
    if not ok_with_cd:
        return "not_dnssec_related"
    if rrsig_expired:
        return "broken_dnssec_chain_expired_rrsig"
    if not ds_matches_dnskey:
        return "broken_dnssec_chain_ds_mismatch"
    return "broken_dnssec_chain_ds_mismatch"


def run():
    # Imported lazily so the pure function above can be tested with no
    # network libraries installed at all.
    import dns.resolver
    import dns.flags
    import requests

    domain = os.environ["DNS_DOMAIN"]
    resolver_ip = os.environ.get("VALIDATING_RESOLVER", "1.1.1.1")
    dry_run = os.environ.get("DRY_RUN", "true").lower() == "true"

    resolver = dns.resolver.Resolver(configure=False)
    resolver.nameservers = [resolver_ip]

    servfail_with_validation = False
    try:
        resolver.resolve(domain, "CAA")
    except dns.resolver.NoAnswer:
        pass
    except dns.exception.DNSException as exc:
        if "SERVFAIL" in str(exc) or exc.__class__.__name__ == "NoNameservers":
            servfail_with_validation = True

    cd_resolver = dns.resolver.Resolver(configure=False)
    cd_resolver.nameservers = [resolver_ip]
    cd_resolver.use_edns(0, dns.flags.DO, 4096)
    ok_with_cd = True
    try:
        query = dns.message.make_query(domain, "CAA", want_dnssec=False)
        query.flags |= dns.flags.CD
        answer = dns.query.udp(query, resolver_ip, timeout=10)
        ok_with_cd = answer.rcode() == 0
    except dns.exception.DNSException:
        ok_with_cd = False

    ds_matches_dnskey = True
    rrsig_expired = False
    try:
        ds_answer = dns.resolver.resolve(domain, "DS")
        dnskey_answer = dns.resolver.resolve(domain, "DNSKEY")
        digests = {str(r).split()[3] for r in ds_answer}
        computed = set()
        for key in dnskey_answer:
            for algo in (1, 2):
                try:
                    ds = dns.dnssec.make_ds(domain, key, algo)
                    computed.add(str(ds).split()[3])
                except Exception:
                    continue
        ds_matches_dnskey = bool(digests & computed)
    except Exception:
        ds_matches_dnskey = False

    try:
        rrsig_answer = dns.resolver.resolve(domain, "DNSKEY", want_dnssec=True)
        import time
        now = int(time.time())
        for rrset in rrsig_answer.response.answer:
            for rr in rrset:
                if rr.rdtype == dns.rdatatype.RRSIG and rr.expiration < now:
                    rrsig_expired = True
    except Exception:
        pass

    verdict = diagnose_caa_dnssec_break(
        servfail_with_validation, ok_with_cd, ds_matches_dnskey, rrsig_expired
    )
    log.info("Diagnosis for %s: %s", domain, verdict)

    if verdict == "ok":
        log.info("CAA resolves fine through a validating resolver. Nothing to do.")
        return

    if verdict == "not_dnssec_related":
        log.warning(
            "SERVFAIL does not clear with +cd, so this looks like a dead "
            "nameserver or network issue, not a broken DNSSEC chain."
        )
        return

    log.warning("Broken DNSSEC chain detected: %s", verdict)

    zone_id = os.environ.get("CLOUDFLARE_ZONE_ID")
    api_token = os.environ.get("CLOUDFLARE_API_TOKEN")
    if not zone_id or not api_token:
        log.info(
            "No Cloudflare credentials set. Fix the DS record at the "
            "registrar, or disable DNSSEC in the correct order, then re-run."
        )
        return

    headers = {"Authorization": f"Bearer {api_token}", "Content-Type": "application/json"}
    resp = requests.get(
        f"https://api.cloudflare.com/client/v4/zones/{zone_id}/dnssec",
        headers=headers, timeout=30,
    )
    resp.raise_for_status()
    status = resp.json()["result"]["status"]
    log.info("Cloudflare zone DNSSEC status is currently: %s", status)

    if dry_run:
        log.info("Dry run: would not change DNSSEC status automatically.")
        return

    disable_dnssec = os.environ.get("DISABLE_DNSSEC", "false").lower() == "true"
    if disable_dnssec and status == "active":
        log.warning(
            "DISABLE_DNSSEC=true, but the DS record must already be removed "
            "at the registrar and its TTL must have expired before this "
            "runs, otherwise this recreates the exact SERVFAIL problem."
        )
        patch = requests.patch(
            f"https://api.cloudflare.com/client/v4/zones/{zone_id}/dnssec",
            headers=headers, json={"status": "disabled"}, timeout=30,
        )
        patch.raise_for_status()
        log.info("Requested Cloudflare to disable DNSSEC for this zone.")
    else:
        log.info(
            "Updating the DS record itself is a registrar action outside "
            "the Cloudflare DNS records API. Get the current DS data from "
            "DNS > Settings > DNSSEC in the Cloudflare dashboard and paste "
            "it into the registrar's DS records page."
        )


if __name__ == "__main__":
    run()
caa-lookup-servfail-dnssec.js
/**
 * Detect a broken DNSSEC chain that turns a CAA lookup into a SERVFAIL and,
 * optionally, check or change a Cloudflare zone's DNSSEC status. Safe to
 * run on a schedule. Stays in dry run until DRY_RUN=false.
 */
import { pathToFileURL } from "node:url";

export function diagnoseCaaDnssecBreak(servfailWithValidation, okWithCd, dsMatchesDnskey, rrsigExpired) {
  // Pure decision function. No DNS I/O, no network calls.
  //
  // servfailWithValidation: true if the CAA query against a validating
  //   resolver (e.g. dig @1.1.1.1 CAA example.com) returned SERVFAIL.
  // okWithCd: true if the same query with checking disabled
  //   (dig @1.1.1.1 +cd CAA example.com) returned NOERROR.
  // dsMatchesDnskey: true if the DS digest at the registrar matches a
  //   hash of the DNSKEY currently signing the zone.
  // rrsigExpired: true if the RRSIG over DNSKEY or CAA has passed its
  //   validity window.
  //
  // Returns one of "ok", "broken_dnssec_chain_ds_mismatch",
  // "broken_dnssec_chain_expired_rrsig", "not_dnssec_related".
  if (!servfailWithValidation) return "ok";
  if (!okWithCd) return "not_dnssec_related";
  if (rrsigExpired) return "broken_dnssec_chain_expired_rrsig";
  return "broken_dnssec_chain_ds_mismatch";
}

async function queryCaaServfail(domain, resolverIp) {
  const dns = await import("node:dns");
  const resolver = new dns.promises.Resolver();
  resolver.setServers([resolverIp]);
  try {
    await resolver.resolveAny(domain);
    return false;
  } catch (err) {
    return err.code === "ESERVFAIL" || err.code === "SERVFAIL";
  }
}

export async function run() {
  // Imported lazily so the pure function above can be tested with no
  // network modules touched at all.
  const { execFile } = await import("node:child_process");
  const { promisify } = await import("node:util");
  const execFileAsync = promisify(execFile);

  const domain = process.env.DNS_DOMAIN;
  const resolverIp = process.env.VALIDATING_RESOLVER || "1.1.1.1";
  const dryRun = (process.env.DRY_RUN || "true").toLowerCase() === "true";

  async function digStatus(args) {
    try {
      const { stdout } = await execFileAsync("dig", args);
      const match = stdout.match(/status:\s*(\w+)/);
      return match ? match[1] : "UNKNOWN";
    } catch {
      return "UNKNOWN";
    }
  }

  const plainStatus = await digStatus(["@" + resolverIp, "CAA", domain]);
  const servfailWithValidation = plainStatus === "SERVFAIL";

  const cdStatus = await digStatus(["@" + resolverIp, "+cd", "CAA", domain]);
  const okWithCd = cdStatus === "NOERROR";

  const dsOut = await digStatus(["DS", domain, "+short"]).catch(() => "");
  const dsMatchesDnskey = true; // full digest comparison is done with dnspython in the Python version
  const rrsigExpired = false; // RRSIG expiry parsing is done with dnspython in the Python version

  const verdict = diagnoseCaaDnssecBreak(servfailWithValidation, okWithCd, dsMatchesDnskey, rrsigExpired);
  console.log(`Diagnosis for ${domain}: ${verdict}`);

  if (verdict === "ok") {
    console.log("CAA resolves fine through a validating resolver. Nothing to do.");
    return;
  }

  if (verdict === "not_dnssec_related") {
    console.warn(
      "SERVFAIL does not clear with +cd, so this looks like a dead " +
      "nameserver or network issue, not a broken DNSSEC chain."
    );
    return;
  }

  console.warn(`Broken DNSSEC chain detected: ${verdict}`);

  const zoneId = process.env.CLOUDFLARE_ZONE_ID;
  const apiToken = process.env.CLOUDFLARE_API_TOKEN;
  if (!zoneId || !apiToken) {
    console.log(
      "No Cloudflare credentials set. Fix the DS record at the registrar, " +
      "or disable DNSSEC in the correct order, then re-run."
    );
    return;
  }

  const headers = { Authorization: `Bearer ${apiToken}`, "Content-Type": "application/json" };
  const res = await fetch(`https://api.cloudflare.com/client/v4/zones/${zoneId}/dnssec`, { headers });
  if (!res.ok) throw new Error(`Cloudflare API returned ${res.status}`);
  const body = await res.json();
  const status = body.result.status;
  console.log(`Cloudflare zone DNSSEC status is currently: ${status}`);

  if (dryRun) {
    console.log("Dry run: would not change DNSSEC status automatically.");
    return;
  }

  const disableDnssec = (process.env.DISABLE_DNSSEC || "false").toLowerCase() === "true";
  if (disableDnssec && status === "active") {
    console.warn(
      "DISABLE_DNSSEC=true, but the DS record must already be removed at " +
      "the registrar and its TTL must have expired before this runs, " +
      "otherwise this recreates the exact SERVFAIL problem."
    );
    const patch = await fetch(`https://api.cloudflare.com/client/v4/zones/${zoneId}/dnssec`, {
      method: "PATCH",
      headers,
      body: JSON.stringify({ status: "disabled" }),
    });
    if (!patch.ok) throw new Error(`Cloudflare API returned ${patch.status}`);
    console.log("Requested Cloudflare to disable DNSSEC for this zone.");
  } else {
    console.log(
      "Updating the DS record itself is a registrar action outside the " +
      "Cloudflare DNS records API. Get the current DS data from DNS > " +
      "Settings > DNSSEC in the Cloudflare dashboard and paste it into " +
      "the registrar's DS records page."
    );
  }
}

if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
  run().catch((err) => { console.error(err); process.exit(1); });
}

Add a test

The diagnosis is the part worth testing on its own, because it decides whether the script blames DNSSEC at all. It takes four plain booleans that were already fetched by dig or dnspython elsewhere, and does no DNS work itself, so the test needs no network and no DNS library.

test_diagnose_caa_dnssec.py
from caa_lookup_servfail_dnssec import diagnose_caa_dnssec_break


def test_ok_when_no_servfail():
    result = diagnose_caa_dnssec_break(False, True, True, False)
    assert result == "ok"


def test_not_dnssec_related_when_cd_also_fails():
    result = diagnose_caa_dnssec_break(True, False, True, False)
    assert result == "not_dnssec_related"


def test_ds_mismatch_when_digests_disagree():
    result = diagnose_caa_dnssec_break(True, True, False, False)
    assert result == "broken_dnssec_chain_ds_mismatch"


def test_expired_rrsig_takes_priority():
    result = diagnose_caa_dnssec_break(True, True, True, True)
    assert result == "broken_dnssec_chain_expired_rrsig"


def test_expired_rrsig_even_if_ds_also_mismatches():
    result = diagnose_caa_dnssec_break(True, True, False, True)
    assert result == "broken_dnssec_chain_expired_rrsig"
caa-lookup-servfail-dnssec.test.js
import { test } from "node:test";
import assert from "node:assert/strict";
import { diagnoseCaaDnssecBreak } from "./caa-lookup-servfail-dnssec.js";

test("ok when no SERVFAIL", () => {
  assert.equal(diagnoseCaaDnssecBreak(false, true, true, false), "ok");
});

test("not dnssec related when +cd also fails", () => {
  assert.equal(diagnoseCaaDnssecBreak(true, false, true, false), "not_dnssec_related");
});

test("ds mismatch when digests disagree", () => {
  assert.equal(diagnoseCaaDnssecBreak(true, true, false, false), "broken_dnssec_chain_ds_mismatch");
});

test("expired rrsig takes priority", () => {
  assert.equal(diagnoseCaaDnssecBreak(true, true, true, true), "broken_dnssec_chain_expired_rrsig");
});

test("expired rrsig even if ds also mismatches", () => {
  assert.equal(diagnoseCaaDnssecBreak(true, true, false, true), "broken_dnssec_chain_expired_rrsig");
});

Case studies

Key rollover

The rollover that finished on one side only

A team rotated their DNSSEC signing key ahead of a scheduled maintenance window. The DNS host applied the new DNSKEY and started signing with it immediately, but the ticket to update the DS record at the registrar sat untouched over a holiday weekend. A routine certificate renewal three days later failed with a DNS problem looking up CAA error.

Running dig @1.1.1.1 CAA showed SERVFAIL, and the same query with +cd succeeded, pointing straight at DNSSEC. Comparing the DS digest at the registrar against the new DNSKEY confirmed the mismatch. Updating the DS record fixed the chain within a few hours of propagation.

Provider migration

DNSSEC left behind during a DNS host switch

A domain moved its nameservers to a new DNS host as part of a broader infrastructure move. DNSSEC had been enabled at the old host, and the migration checklist covered nameservers, records, and TTLs, but nobody remembered the DS record was a separate, registrar-side setting that also needed attention.

The new host signed the zone with a completely different key, so the old DS record no longer matched anything. DNSViz showed a clear red error at the DS-to-DNSKEY link. Since the team preferred not to manage DNSSEC after the move, they removed the DS record at the registrar, waited out the TTL, then disabled signing at the new host, in that order.

What good looks like

Once the DS record matches the live DNSKEY, or DNSSEC has been removed the safe way, a CAA query against any validating resolver returns a real answer instead of SERVFAIL, and DNSViz shows a fully green chain with no error nodes anywhere. Certificate renewals go back to failing or succeeding based on the actual CAA policy, not on a chain that was never trustworthy in the first place.

FAQ

Why does my certificate renewal fail with a DNS problem looking up CAA error?

Your domain has DNSSEC turned on, but the DS record at the registrar no longer matches the DNSKEY that is actually signing the zone at your DNS host. A validating resolver cannot confirm the CAA record is genuine, so it returns SERVFAIL, and the certificate authority is required to treat that as unable to check CAA policy and refuse to issue.

How do I tell if SERVFAIL is caused by DNSSEC and not a dead nameserver?

Run the same CAA query twice against a validating resolver such as 1.1.1.1, once as normal and once with the +cd flag, which disables DNSSEC validation. If the plain query returns SERVFAIL but the +cd query succeeds, the nameservers are fine and DNSSEC validation is the cause.

What is the safe order of steps when disabling DNSSEC to fix this?

Remove the DS record at the registrar first, then wait at least one and a half times the DS record's TTL for caches to expire it, roughly 36 hours for a 24 hour TTL, and only then remove the DNSKEY and turn off signing at the DNS host. Doing it in the other order leaves a DS record with no matching key, which causes the exact SERVFAIL this article describes.

Related field notes

Citations

On the problem:

  1. CA/Browser Forum Ballot SC-085v2: Require Validation of DNSSEC (when present) for CAA and DCV Lookups. cabforum.org/2025/06/18/ballot-sc-085v2
  2. Let's Encrypt docs: Certification Authority Authorization (CAA). letsencrypt.org/docs/caa
  3. RFC 8659: DNS Certification Authority Authorization (CAA) Resource Record. datatracker.ietf.org/doc/html/rfc8659

On the solution:

  1. Cloudflare DNS docs: Troubleshooting DNSSEC. developers.cloudflare.com/dns/dnssec/troubleshooting
  2. Cloudflare Registrar docs: Domain Name System Security Extensions (DNSSEC). developers.cloudflare.com/registrar/get-started/enable-dnssec
  3. DNSViz: DNSSEC chain of trust visualization tool. dnsviz.net

Stuck on a tricky one?

If you have a DNS, email deliverability, or certificate problem you would rather hand off, this is the kind of work I do. Message me and we can work through it together.

Contact me on LinkedIn

Did this fix your certificate renewal?

If this saved you a stalled renewal or a scramble before a certificate expired, you can buy me a coffee. It is the best way to keep these field notes free and growing.

Buy me a coffee on Ko-fi

Back to all DNS and Domains field notes