caddyserver/caddy · error
on-demand TLS cannot be enabled without a permission module
Error message
on-demand TLS cannot be enabled without a permission module to prevent abuse; please refer to documentation for details
What it means
On-demand TLS was enabled for an automation policy that covers wildcard/default subjects without any permission module (ask endpoint / http directive in on_demand_tls, or a permission module), no explicitly-configured managers, and not solely the internal issuer. Because unbounded on-demand issuance has been abused to hammer ACME CAs, Caddy fails closed at config provisioning time and requires an explicit abuse-prevention mechanism.
Source
Thrown at modules/caddytls/automation.go:307
if storage == nil {
storage = tlsApp.ctx.Storage()
}
// on-demand TLS
var ond *certmagic.OnDemandConfig
if ap.OnDemand || len(ap.Managers) > 0 {
// permission module is now required after a number of negligence cases that allowed abuse;
// but it may still be optional for explicit subjects (bounded, non-wildcard), for the
// internal issuer since it doesn't cause public PKI pressure on ACME servers; subtly, it
// is useful to allow on-demand TLS to be enabled so Managers can be used, but to still
// prevent issuance from Issuers (when Managers don't provide a certificate) if there's no
// permission module configured
noProtections := ap.isWildcardOrDefault() && !ap.onlyInternalIssuer() && (tlsApp.Automation == nil || tlsApp.Automation.OnDemand == nil || tlsApp.Automation.OnDemand.permission == nil)
failClosed := noProtections && !ap.hadExplicitManagers // don't allow on-demand issuance (other than implicit managers) if no managers have been explicitly configured
if noProtections {
if !ap.hadExplicitManagers {
// no managers, no explicitly-configured permission module, this is a config error
return certmagic.Config{}, fmt.Errorf("on-demand TLS cannot be enabled without a permission module to prevent abuse; please refer to documentation for details")
}
// allow on-demand to be enabled but only for the purpose of the Managers; issuance won't be allowed from Issuers
tlsApp.logger.Warn("on-demand TLS can only get certificates from the configured external manager(s) because no ask endpoint / permission module is specified")
}
ond = &certmagic.OnDemandConfig{
DecisionFunc: func(ctx context.Context, name string) error {
if failClosed {
return fmt.Errorf("no permission module configured; certificates not allowed except from external Managers")
}
if tlsApp.Automation == nil || tlsApp.Automation.OnDemand == nil {
return nil
}
// logging the remote IP can be useful for servers that want to count
// attempts from clients to detect patterns of abuse -- it should NOT be
// used solely for decision making, however
var remoteIP string
if hello, ok := ctx.Value(certmagic.ClientHelloInfoCtxKey).(*tls.ClientHelloInfo); ok && hello != nil {View on GitHub (pinned to 50e54ee279)
Solutions
- Add a permission module: in the Caddyfile global options set on_demand_tls { ask <endpoint> } pointing to a 2xx-returning endpoint that approves names, or configure a permission module in JSON under apps.tls.automation.on_demand.permission.
- Or add an explicit certificate manager (get_certificate) so issuance is bounded by the manager, if managers fit the use case.
- Or restrict the policy to explicit, non-wildcard subjects and/or use only the internal issuer, which are exempt.
- As a last resort, disable on_demand and list subjects explicitly.
Example fix
# before
{
on_demand_tls {
# no ask / permission
}
}
example.com {
tls {
on_demand
}
}
# after
{
on_demand_tls {
ask http://localhost:5555/check
}
}
example.com {
tls {
on_demand
}
} Defensive patterns
Strategy: validation
Validate before calling
// Config-time check mirroring Caddy's rule.
needsPermission := policy.isWildcardOrDefault && !onlyInternalIssuer(policy)
hasPermission := tlsApp.Automation != nil && tlsApp.Automation.OnDemand != nil && tlsApp.Automation.OnDemand.permission != nil
if (policy.OnDemand || len(policy.Managers) > 0) && needsPermission && !hasPermission && !hadExplicitManagers {
return errors.New("on-demand requires an ask endpoint / permission module")
} Prevention
- Always pair on_demand with on_demand_tls { ask ... } in configs.
- Have the ask endpoint return non-2xx for unknown names to bound issuance.
- Run `caddy validate` on configs after every Caddy upgrade; on-demand rules tightened over time.
When it happens
Trigger: Configuring on_demand_tls without permission (e.g. missing ask URL), or tls { on_demand } on a catch-all/wildcard site without an ask endpoint and without get_certificate managers. The check noProtections = isWildcardOrDefault() && !onlyInternalIssuer() && no permission module, combined with no explicit managers, triggers the error.
Common situations: Upgrading from older Caddy versions where on_demand without ask was allowed; enabling on-demand TLS for dynamic tenant subdomains and forgetting the ask endpoint; using the internal issuer alongside a wildcard but with a non-internal issuer also present.
Understand the failure class
- SSL/TLS and certificate errors — how TLS handshakes and certificate validation fail.
Related errors
- no permission module configured; certificates not allowed ex
- cannot enable remote admin without a certificate cache; conf
- expanding email address '%s': %v
- DNS challenge enabled, but no DNS provider configured
- your email address is required to use ZeroSSL's ACME endpoin
AI-assisted analysis of caddyserver/caddy@50e54ee279 (2026-08-15).
Data as JSON: /api/errors/4b4279633cbe800c.
Report an issue: GitHub.