caddyserver/caddy · error

expanding DNS override domain '%s': %v

Error message

expanding DNS override domain '%s': %v

What it means

Returned by ACMEIssuer.Provision (modules/caddytls/acmeissuer.go:192) when the DNS-01 challenge override domain (DNS override_domain, JSON "override_domain") contains a placeholder that fails expansion - unknown env var or malformed placeholder. This option redirects challenge DNS records to a different zone (e.g. when using a DNS provider that only manages a parent domain), and provisioning stops if its value cannot be resolved.

Source

Thrown at modules/caddytls/acmeissuer.go:192

			}
			iss.ExternalAccount.MACKey = macKey
		}
	}

	// expand account key, if non-empty
	if iss.AccountKey != "" {
		accountKey, err := repl.ReplaceOrErr(iss.AccountKey, true, true)
		if err != nil {
			return fmt.Errorf("expanding account key PEM '%s': %v", iss.AccountKey, err)
		}
		iss.AccountKey = accountKey
	}

	// expand DNS override domain, if non-empty
	if iss.Challenges != nil && iss.Challenges.DNS != nil && iss.Challenges.DNS.OverrideDomain != "" {
		overrideDomain, err := repl.ReplaceOrErr(iss.Challenges.DNS.OverrideDomain, true, true)
		if err != nil {
			return fmt.Errorf("expanding DNS override domain '%s': %v", iss.Challenges.DNS.OverrideDomain, err)
		}
		iss.Challenges.DNS.OverrideDomain = overrideDomain
	}

	// DNS challenge provider, if not already established
	if iss.Challenges != nil && iss.Challenges.DNS != nil && iss.Challenges.DNS.solver == nil {
		var prov certmagic.DNSProvider
		if iss.Challenges.DNS.ProviderRaw != nil {
			// a challenge provider has been locally configured - use it
			val, err := ctx.LoadModule(iss.Challenges.DNS, "ProviderRaw")
			if err != nil {
				return fmt.Errorf("loading DNS provider module: %v", err)
			}
			prov = val.(certmagic.DNSProvider)
		} else if tlsAppIface, err := ctx.AppIfConfigured("tls"); err == nil {
			// no locally configured DNS challenge provider, but if there is
			// a global DNS module configured with the TLS app, use that
			tlsApp := tlsAppIface.(*TLS)

View on GitHub (pinned to 50e54ee279)

Solutions

  1. Export DNS_ZONE in the service environment and restart Caddy
  2. Or write the literal zone name: override_domain example.com
  3. Fix placeholder syntax to exactly {env.DNS_ZONE}
  4. Validate the whole config with caddy validate before deploy

Example fix

# before
 *.example.com {
   tls {
     dns cloudflare {env.CF_TOKEN} {
       override_domain {env.DNS_OVR}   # unset
     }
   }
 }

# after
 *.example.com {
   tls {
     dns cloudflare {env.CF_TOKEN} {
       override_domain example.com
     }
   }
 }
Defensive patterns

Strategy: validation

Validate before calling

if strings.Contains(cfgText, "{env.DNS_ZONE}") && os.Getenv("DNS_ZONE") == "" {
    return errors.New("DNS_ZONE referenced by override_domain but not set")
}

Try / catch

if err := issuer.Provision(ctx); err != nil {
    if strings.Contains(err.Error(), "expanding DNS override domain") {
        // replace with the literal parent zone or set the env var
    }
    return err
}

Prevention

When it happens

Trigger: Configuring challenges { dns { override_domain {env.DNS_ZONE} } } where DNS_ZONE is not set in Caddy's environment, or the placeholder has a typo/broken braces.

Common situations: Wildcard cert setups (*.example.com) using override_domain to answer in example.com; the env var exists in dev but not in the deployed unit; variable renamed when the DNS setup moved to another provider.

Related errors


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