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
- 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.
- 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.
- 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
- Expect unknown-SNI handshakes to fail by design in managers-only setups; monitor, don't 'fix' with open issuance.
- If users report handshake failures for legitimate names, add a permission module covering those names.
- Log DecisionFunc denials at the edge to distinguish scans from real user impact.
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
- SSL/TLS and certificate errors — how TLS handshakes and certificate validation fail.
Related errors
- on-demand TLS cannot be enabled without a permission module
- dropping connection
- no server TLS configuration available for ClientHello: %+v
- handshake context: %v
- no client certificate provided
AI-assisted analysis of caddyserver/caddy@50e54ee279 (2026-08-15).
Data as JSON: /api/errors/7af0337d79687e51.
Report an issue: GitHub.