caddyserver/caddy · error
on-demand TLS config conflict: both 'ask' endpoint and a 'pe
Error message
on-demand TLS config conflict: both 'ask' endpoint and a 'permission' module are specified; 'ask' is deprecated, so use only the permission module
What it means
On-demand TLS supports two permission mechanisms: the deprecated 'ask' endpoint URL and the newer 'permission' module. When tls.automation.on_demand sets both 'ask' and a 'permission' module, Caddy refuses to guess which applies and aborts provisioning. 'ask' is deprecated in favor of the http permission module, which supersedes it.
Source
Thrown at modules/caddytls/tls.go:280
certCacheMu.RUnlock()
for _, loader := range t.certificateLoaders {
certs, err := loader.LoadCertificates()
if err != nil {
return fmt.Errorf("loading certificates: %v", err)
}
for _, cert := range certs {
hash, err := magic.CacheUnmanagedTLSCertificate(ctx, cert.Certificate, cert.Tags)
if err != nil {
return fmt.Errorf("caching unmanaged certificate: %v", err)
}
t.loaded[hash] = ""
}
}
// on-demand permission module
if t.Automation != nil && t.Automation.OnDemand != nil && t.Automation.OnDemand.PermissionRaw != nil {
if t.Automation.OnDemand.Ask != "" {
return fmt.Errorf("on-demand TLS config conflict: both 'ask' endpoint and a 'permission' module are specified; 'ask' is deprecated, so use only the permission module")
}
val, err := ctx.LoadModule(t.Automation.OnDemand, "PermissionRaw")
if err != nil {
return fmt.Errorf("loading on-demand TLS permission module: %v", err)
}
t.Automation.OnDemand.permission = val.(OnDemandPermission)
}
// automation/management policies
if t.Automation == nil {
t.Automation = new(AutomationConfig)
}
t.Automation.defaultPublicAutomationPolicy = new(AutomationPolicy)
err = t.Automation.defaultPublicAutomationPolicy.Provision(t)
if err != nil {
return fmt.Errorf("provisioning default public automation policy: %v", err)
}
for n := range t.automateNames {View on GitHub (pinned to 50e54ee279)
Solutions
- Delete the 'ask' entry and keep only the permission module (e.g. permission module 'http' with endpoint set to the old ask URL)
- If you must keep 'ask' temporarily, remove the 'permission' block entirely
- Run 'caddy validate' after editing to confirm the conflict is gone
Example fix
// before
"on_demand": {"ask": "http://localhost:5555/check", "permission": {"module": "http", "endpoint": "http://localhost:5555/check"}}
// after
"on_demand": {"permission": {"module": "http", "endpoint": "http://localhost:5555/check"}} Defensive patterns
Strategy: validation
Validate before calling
jq '.apps.tls.automation.on_demand // {} | select(.ask != null and .permission != null)' Caddyfile.json
# non-empty output means the conflict will abort provisioning Prevention
- Never combine 'ask' with 'permission'; migrate fully to the permission module
- Add a config lint step that rejects on_demand blocks containing both keys
When it happens
Trigger: JSON config containing {"on_demand": {"ask": "http://...", "permission": {...}}} — both keys non-empty under tls.automation.on_demand. In Caddyfile terms, combining the legacy 'ask' global option with an 'on_demand_tls permission' block.
Common situations: Configs migrated from older Caddy versions that already had 'ask' and then added a permission module per new docs; copy-pasting examples from different documentation eras into one config.
Understand the failure class
- SSL/TLS and certificate errors — how TLS handshakes and certificate validation fail.
Related errors
- loading on-demand TLS permission module: %v
- preparing 'ask' endpoint: %v
- provisioning 'ask' module: %v
- consolidating TLS connection policies for server %d: %v
- server listening on %v is HTTP, but attempts to configure TL
AI-assisted analysis of caddyserver/caddy@50e54ee279 (2026-08-15).
Data as JSON: /api/errors/2d827264c689a4d5.
Report an issue: GitHub.