caddyserver/caddy · warning

no permission module configured; certificates not allowed ex

Error message

no permission module configured; certificates not allowed except from external Managers

What it means

This is the runtime twin of the config-time on-demand guard: the DecisionFunc installed in certmagic's OnDemandConfig rejects a certificate request because failClosed is true — the policy has no permission module but does have explicitly-configured managers. On-demand TLS is permitted only so the managers can supply certificates; actual issuance from ACME/internal issuers is denied at handshake time.

Source

Thrown at modules/caddytls/automation.go:315

		// 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 {
					if remote := hello.Conn.RemoteAddr(); remote != nil {
						remoteIP, _, _ = net.SplitHostPort(remote.String())
					}
				}
				if c := tlsApp.logger.Check(zapcore.DebugLevel, "asking for permission for on-demand certificate"); c != nil {
					c.Write(
						zap.String("remote_ip", remoteIP),
						zap.String("domain", name),

View on GitHub (pinned to 50e54ee279)

Solutions

  1. Expected behavior in most cases: only names provided by the configured manager(s) are served; ensure the requested name is actually available from the manager.
  2. If real on-demand ACME issuance is needed, add an on_demand_tls permission module (ask endpoint or permission module) so the DecisionFunc can approve names.
  3. Add the specific hostnames as explicit subjects on a normal (non-on-demand) policy if they should be issued up front.

Example fix

# before: managers-only on-demand, handshake for unknown name fails
{
	on_demand_tls {
		# nothing
	}
}

# after: permit on-demand issuance via ask endpoint
{
	on_demand_tls {
		ask http://localhost:5555/check
	}
}
Defensive patterns

Strategy: validation

Validate before calling

// If issuance (not just managers) is expected, require a permission module up front.
if len(policy.Managers) > 0 && (tlsApp.Automation == nil || tlsApp.Automation.OnDemand == nil || tlsApp.Automation.OnDemand.permission == nil) {
    log.Warn("on-demand issuance is fail-closed: only manager-provided certs will be served")
}

Prevention

When it happens

Trigger: A TLS handshake presents a ServerName for which no configured manager returns a certificate, and the policy lacks an ask endpoint/permission module, so the DecisionFunc returns this error instead of letting certmagic issue a new public cert.

Common situations: A get_certificate (e.g. Tailscale) setup where a client requests a name the manager does not cover; scan/bot traffic probing arbitrary SNI names on a server configured with managers-only on-demand TLS.

Understand the failure class

Related errors


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