caddyserver/caddy · error

acme_dns specified without DNS provider config, but no provi

Error message

acme_dns specified without DNS provider config, but no provider specified with 'dns' global option

What it means

fillInGlobalACMEDefaults applies global 'acme_dns' to ACME issuers. parseOptDNS allows writing 'acme_dns' with NO arguments — it parses to nil, meaning 'inherit the globally-defined dns provider'. This error fires when that is the case (acme_dns key present, value nil) AND the issuer's DNS challenge has no provider AND options['dns'] is also nil: there is no provider anywhere to wire in, so the adapter errors instead of producing a providerless DNS challenge.

Source

Thrown at caddyconfig/httpcaddyfile/tlsapp.go:548

	globalACMEEAB := options["acme_eab"]
	globalPreferredChains := options["preferred_chains"]
	globalCertLifetime := options["cert_lifetime"]
	globalHTTPPort, globalHTTPSPort := options["http_port"], options["https_port"]
	globalDefaultBind := options["default_bind"]

	if globalEmail != nil && acmeIssuer.Email == "" {
		acmeIssuer.Email = globalEmail.(string)
	}
	if globalACMECA != nil && acmeIssuer.CA == "" {
		acmeIssuer.CA = globalACMECA.(string)
	}
	if globalACMECARoot != nil && !slices.Contains(acmeIssuer.TrustedRootsPEMFiles, globalACMECARoot.(string)) {
		acmeIssuer.TrustedRootsPEMFiles = append(acmeIssuer.TrustedRootsPEMFiles, globalACMECARoot.(string))
	}
	if globalACMEDNSok && (acmeIssuer.Challenges == nil || acmeIssuer.Challenges.DNS == nil || acmeIssuer.Challenges.DNS.ProviderRaw == nil) {
		globalDNS := options["dns"]
		if globalDNS == nil && globalACMEDNS == nil {
			return fmt.Errorf("acme_dns specified without DNS provider config, but no provider specified with 'dns' global option")
		}
		if acmeIssuer.Challenges == nil {
			acmeIssuer.Challenges = new(caddytls.ChallengesConfig)
		}
		if acmeIssuer.Challenges.DNS == nil {
			acmeIssuer.Challenges.DNS = new(caddytls.DNSChallengeConfig)
		}
		if globalACMEDNS != nil && acmeIssuer.Challenges.DNS.ProviderRaw == nil {
			// Set a global DNS provider if `acme_dns` is set
			acmeIssuer.Challenges.DNS.ProviderRaw = caddyconfig.JSONModuleObject(globalACMEDNS, "name", globalACMEDNS.(caddy.Module).CaddyModule().ID.Name(), nil)
		}
	}
	if globalACMEEAB != nil && acmeIssuer.ExternalAccount == nil {
		acmeIssuer.ExternalAccount = globalACMEEAB.(*acme.EAB)
	}
	if globalPreferredChains != nil && acmeIssuer.PreferredChains == nil {
		acmeIssuer.PreferredChains = globalPreferredChains.(*caddytls.ChainPreference)
	}

View on GitHub (pinned to 50e54ee279)

Solutions

  1. Give acme_dns its provider: 'acme_dns cloudflare {env.CF_API_TOKEN}'.
  2. Or keep bare 'acme_dns' and add the global 'dns cloudflare {env.CF_API_TOKEN}' option it inherits from.
  3. Remove 'acme_dns' entirely if no DNS challenge is wanted.
  4. Ensure the DNS provider plugin is in the build ('caddy list-modules | grep dns.providers').

Example fix

# before
{
  acme_dns
}

# after
{
  acme_dns cloudflare {env.CF_API_TOKEN}
}

# or
{
  dns cloudflare {env.CF_API_TOKEN}
  acme_dns
}
Defensive patterns

Strategy: validation

Validate before calling

grep -nE '^\s*acme_dns\s*$' Caddyfile  # bare acme_dns with no provider and no global 'dns' option will fail

Prevention

When it happens

Trigger: A global block containing bare 'acme_dns' (no module name/args) while no global 'dns <provider> ...' option exists anywhere in the config.

Common situations: Copy-pasting an acme_dns line and losing its provider argument; intending to centralize the provider via 'dns' but forgetting that option; templates that emit 'acme_dns' unconditionally.

Related errors


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