caddyserver/caddy · error
loading DNS provider module
Error message
loading DNS provider module: %v
What it means
Returned by ACMEIssuer.Provision (modules/caddytls/acmeissuer.go:204) when ctx.LoadModule fails to instantiate the configured DNS challenge provider module (the dns directive's provider, e.g. cloudflare). Wrapped causes: the provider module name is not registered (plugin not compiled into the binary), the module's own provisioning failed (bad credentials), or the JSON caddy module field is malformed.
Solutions
- Run caddy list-modules and confirm the DNS provider (e.g. dns.providers.cloudflare) is present; if missing, build with xcaddy: xcaddy build --with github.com/caddy-dns/cloudflare
- Check the wrapped error for credential validation failures and correct the provider options (token, profile, etc.)
- Fix the provider name to match the module's documented name exactly
- Keep credentials in env placeholders ({env.CF_API_TOKEN}) and ensure they are set in the service environment
Example fix
# before: stock binary, no plugin
example.com {
tls {
dns cloudflare {env.CF_API_TOKEN}
}
}
# after: build with the plugin, keep the config
xcaddy build --with github.com/caddy-dns/cloudflare
# then deploy the new binary; config unchanged Defensive patterns
Strategy: validation
Validate before calling
# confirm the provider module exists in the binary before deploying config
caddy list-modules | grep -q 'dns.providers.cloudflare' || {
echo "cloudflare DNS plugin missing; rebuild with xcaddy"; exit 1; }
# and confirm credentials resolve
test -n "$CF_API_TOKEN" || { echo "CF_API_TOKEN unset"; exit 1; } Try / catch
if err := issuer.Provision(ctx); err != nil {
if strings.Contains(err.Error(), "loading DNS provider module") {
// unwrap for the real cause: missing plugin (rebuild) or bad creds (fix options)
}
return err
} Prevention
- Pin and rebuild custom binaries with xcaddy in CI; record the --with set next to the config
- Run caddy list-modules as a post-build assertion
- Keep provider tokens in env placeholders and validate them in the same pipeline
- When upgrading plugins, re-read their option schema - names change between majors
When it happens
Trigger: Using tls { dns <provider> ... } with a provider name that does not exist in this Caddy build (e.g. a DNS plugin from xcaddy that was not included), or the provider's Provision failing - typically invalid/missing API credentials for cloudflare/route53/etc.
Common situations: Swapping the stock caddy binary (no plugins) for a config written for a plugin build; plugin name typos (cloudflare vs cloud_flare); provider credentials given as a literal that fails validation; version drift where a plugin changed its option names after an upgrade.
Related errors
- failed to load network_proxy module
- acme_dns specified without DNS provider config, but no…
- DNS challenge enabled, but no DNS provider configured
- expanding DNS override domain
- filling in global issuer defaults for AP
AI-assisted analysis of caddyserver/caddy@50e54ee279 (2026-08-15).
Data as JSON: /api/errors/61324bbee01b6687.
Report an issue: GitHub.
Appendix: source
Thrown at modules/caddytls/acmeissuer.go:204
}
// 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)
if tlsApp.dns != nil {
prov = tlsApp.dns.(certmagic.DNSProvider)
}
}
if prov == nil {
return fmt.Errorf("DNS challenge enabled, but no DNS provider configured")
}
iss.Challenges.DNS.solver = &certmagic.DNS01Solver{
DNSManager: certmagic.DNSManager{
DNSProvider: prov,
TTL: time.Duration(iss.Challenges.DNS.TTL),
PropagationDelay: time.Duration(iss.Challenges.DNS.PropagationDelay),View on GitHub (pinned to 50e54ee279)