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
- Export DNS_ZONE in the service environment and restart Caddy
- Or write the literal zone name: override_domain example.com
- Fix placeholder syntax to exactly {env.DNS_ZONE}
- 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
- Override zones are stable per-domain: prefer literals over placeholders
- Keep DNS challenge options in a shared snippet reviewed together with the DNS provider creds
- Add placeholder-vs-env consistency checks to config CI
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
- DNS challenge enabled, but no DNS provider configured
- filling in global issuer defaults for AP %d, issuer %d: %v
- acme_dns specified without DNS provider config, but no provi
- filling in global issuer defaults for issuer %d: %v
- expanding email address '%s': %v
AI-assisted analysis of caddyserver/caddy@50e54ee279 (2026-08-15).
Data as JSON: /api/errors/81d6fb1f452e598b.
Report an issue: GitHub.