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

  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.