caddyserver/caddy · warning · PublishECHConfigListErrors

could not determine zone for domain: %w (domain=%s nameserve

Error message

could not determine zone for domain: %w (domain=%s nameservers=%v)

What it means

While publishing an HTTPS DNS record carrying ECH data, Caddy must determine the DNS zone (cutting point) for each inner name using certmagic.FindZoneByFQDN against the recursive nameservers (from resolv.conf / defaults). This per-domain error records that SOA discovery failed — DNS query errors, no SOA found up the hierarchy, or unreachable resolvers. It is collected into PublishECHConfigListErrors keyed by domain, not aborting the whole loop.

Source

Thrown at modules/caddytls/ech.go:830

// because it is unlikely that specific configuration, such as an API key,
// is relevant to unique key use as an ECH config publisher.
func (dnsPub ECHDNSPublisher) PublisherKey() string {
	return string(dnsPub.provider.(caddy.Module).CaddyModule().ID)
}

// PublishECHConfigList publishes the given ECH config list (as binary) to the given DNS names.
// If there is an error, it may be of type PublishECHConfigListErrors, detailing
// potentially multiple errors keyed by associated innerName.
func (dnsPub *ECHDNSPublisher) PublishECHConfigList(ctx context.Context, innerNames []string, configListBin []byte) error {
	nameservers := certmagic.RecursiveNameservers(nil) // TODO: we could make resolvers configurable

	errs := make(PublishECHConfigListErrors)

nextName:
	for _, domain := range innerNames {
		zone, err := certmagic.FindZoneByFQDN(ctx, dnsPub.logger, domain, nameservers)
		if err != nil {
			errs[domain] = fmt.Errorf("could not determine zone for domain: %w (domain=%s nameservers=%v)", err, domain, nameservers)
			continue
		}

		relName := libdns.RelativeName(domain+".", zone)

		// get existing records for this domain; we need to make sure another
		// record exists for it so we don't accidentally trample a wildcard; we
		// also want to get any HTTPS record that may already exist for it so
		// we can augment the ech SvcParamKey with any other existing SvcParams
		recs, err := dnsPub.provider.GetRecords(ctx, zone)
		if err != nil {
			errs[domain] = fmt.Errorf("unable to get existing DNS records to publish ECH data to HTTPS DNS record: %w", err)
			continue
		}
		var httpsRec libdns.ServiceBinding
		var nameHasExistingRecord bool
		for _, rec := range recs {
			rr := rec.RR()

View on GitHub (pinned to 50e54ee279)

Solutions

  1. Verify the resolver path: dig SOA <domain> from the same host/container; fix resolv.conf or egress rules.
  2. Confirm the domain is delegated and has an SOA in public DNS (dig SOA example.com +trace).
  3. Remove non-public/internal-only names from the ECH-enabled sites, since HTTPS-record publication requires public DNS.
  4. Check the nameservers= list in the error to see which resolvers were used and adjust host resolver config.

Example fix

# before: container blocks outbound DNS
$ dig SOA example.com
;; connection timed out

# after: allow DNS egress or point to a working resolver
$ dig SOA example.com
example.com. 3600 IN SOA ns1.example.com. ...
Defensive patterns

Strategy: fallback

Validate before calling

// Pre-check zone resolvability before enabling ECH publishing.
func zoneResolvable(domain string) error {
    ns, _ := certmagic.RecursiveNameservers(nil)
    _, err := certmagic.FindZoneByFQDN(context.Background(), logger, domain, ns)
    return err
}

Try / catch

var perrs caddytls.PublishECHConfigListErrors
if errors.As(err, &perrs) {
    for domain, derr := range perrs {
        if strings.Contains(derr.Error(), "could not determine zone") {
            // skip this domain, keep others; fix resolver/delegation then reload
        }
    }
}

Prevention

When it happens

Trigger: PublishECHConfigList for an innerName whose zone lookup fails: resolver unreachable (egress blocked on UDP/TCP 53), SERVFAIL from recursive resolvers, name with no SOA (not delegated), or a nameserver list that only resolves internal domains.

Common situations: Containers/VMs with broken /etc/resolv.conf; firewall blocking outbound DNS so public SOA lookup fails; trying to publish ECH for a name that is not actually delegated in public DNS; DNSSEC validation failure upstream.

Related errors


AI-assisted analysis of caddyserver/caddy@50e54ee279 (2026-08-15). Data as JSON: /api/errors/c6cc6acb3be8ed67. Report an issue: GitHub.