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

  1. 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.
  2. Or add an explicit certificate manager (get_certificate) so issuance is bounded by the manager, if managers fit the use case.
  3. Or restrict the policy to explicit, non-wildcard subjects and/or use only the internal issuer, which are exempt.
  4. 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

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

Related errors


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