caddyserver/caddy · error
automation policy %d is the second policy that acts as defau
Error message
automation policy %d is the second policy that acts as default/catch-all, but will never be used
What it means
During TLS app validation, Caddy enforces that at most one automation policy may have an empty subject list (the default/catch-all policy), because policy matching is first-match-wins: a second catch-all is dead configuration that can never apply. This error fires when two or more policies in automation.policies have no subjects.
Source
Thrown at modules/caddytls/tls.go:380
}
}
return nil
}
// Validate validates t's configuration.
func (t *TLS) Validate() error {
if t.Automation != nil {
// ensure that host aren't repeated; since only the first
// automation policy is used, repeating a host in the lists
// isn't useful and is probably a mistake; same for two
// catch-all/default policies
var hasDefault bool
hostSet := make(map[string]int)
for i, ap := range t.Automation.Policies {
if len(ap.subjects) == 0 {
if hasDefault {
return fmt.Errorf("automation policy %d is the second policy that acts as default/catch-all, but will never be used", i)
}
hasDefault = true
}
for _, h := range ap.subjects {
if first, ok := hostSet[h]; ok {
return fmt.Errorf("automation policy %d: cannot apply more than one automation policy to host: %s (first match in policy %d)", i, h, first)
}
hostSet[h] = i
}
}
}
if t.Cache != nil {
if t.Cache.Capacity < 0 {
return fmt.Errorf("cache capacity must be >= 0")
}
}
return nil
}View on GitHub (pinned to 50e54ee279)
Solutions
- Give all but one policy explicit subjects
- Or delete the redundant catch-all policy (index in the message) and merge its settings into the single default
- Remember order matters: place specific-subject policies before the catch-all
Example fix
// before
"policies": [
{"issuers": [{"module": "acme"}]},
{"subjects": ["example.com"]},
{"issuers": [{"module": "internal"}]}
]
// after
"policies": [
{"subjects": ["example.com"], "issuers": [{"module": "internal"}]},
{"issuers": [{"module": "acme"}]}
] Defensive patterns
Strategy: validation
Validate before calling
catchAlls := 0
for i, p := range tlsApp.Automation.Policies {
if len(p.SubjectsRaw) == 0 {
catchAlls++
if catchAlls > 1 {
return fmt.Errorf("policy %d is a redundant catch-all", i)
}
}
} Prevention
- Design policies with exactly one default (no subjects) and all others with explicit subjects
- Put the catch-all policy last
When it happens
Trigger: automation.policies containing e.g. [{issuers: A}, {subjects: ["x.com"]}, {issuers: B}] — both the first and third entries are subject-less; the third (index reported as %d) is unreachable.
Common situations: Appending a global policy for a new issuer without realizing an earlier policy already acts as default; merging configs that each had their own default policy; ordering mistakes where a specific policy was meant to be global and vice versa.
Related errors
- provisioning automation policy %d: %v
- automation policy %d: cannot apply more than one automation
- cache capacity must be >= 0
- parsing listener address: %v
- must be exactly one listener address; cannot listen on: %s
AI-assisted analysis of caddyserver/caddy@50e54ee279 (2026-08-15).
Data as JSON: /api/errors/7d33bff965d95f0e.
Report an issue: GitHub.