caddyserver/caddy · error

automation policy is the second policy that acts as…

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.

Solutions

  1. Give all but one policy explicit subjects
  2. Or delete the redundant catch-all policy (index in the message) and merge its settings into the single default
  3. 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

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


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

Appendix: 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)