caddyserver/caddy · error

two policies with same match criteria have conflicting clien

Error message

two policies with same match criteria have conflicting client auth configuration: %+v vs. %+v

What it means

Two connection policies matching the same SNI criteria disagree on client authentication: one has ClientAuthentication nil (no client cert requirements) while the other defines one. Whether a client certificate is required is not mergeable, so adaptation fails showing both configurations with %+v.

Source

Thrown at caddyconfig/httpcaddyfile/httptype.go:1266

			// if they have the same matcher, try to reconcile each field: either they must
			// be identical, or we have to be able to combine them safely
			if reflect.DeepEqual(cps[i].MatchersRaw, cps[j].MatchersRaw) {
				if len(cps[i].ALPN) > 0 &&
					len(cps[j].ALPN) > 0 &&
					!reflect.DeepEqual(cps[i].ALPN, cps[j].ALPN) {
					return nil, fmt.Errorf("two policies with same match criteria have conflicting ALPN: %v vs. %v",
						cps[i].ALPN, cps[j].ALPN)
				}
				if len(cps[i].CipherSuites) > 0 &&
					len(cps[j].CipherSuites) > 0 &&
					!reflect.DeepEqual(cps[i].CipherSuites, cps[j].CipherSuites) {
					return nil, fmt.Errorf("two policies with same match criteria have conflicting cipher suites: %v vs. %v",
						cps[i].CipherSuites, cps[j].CipherSuites)
				}
				if cps[i].ClientAuthentication == nil &&
					cps[j].ClientAuthentication != nil &&
					!reflect.DeepEqual(cps[i].ClientAuthentication, cps[j].ClientAuthentication) {
					return nil, fmt.Errorf("two policies with same match criteria have conflicting client auth configuration: %+v vs. %+v",
						cps[i].ClientAuthentication, cps[j].ClientAuthentication)
				}
				if len(cps[i].Curves) > 0 &&
					len(cps[j].Curves) > 0 &&
					!reflect.DeepEqual(cps[i].Curves, cps[j].Curves) {
					return nil, fmt.Errorf("two policies with same match criteria have conflicting curves: %v vs. %v",
						cps[i].Curves, cps[j].Curves)
				}
				if cps[i].DefaultSNI != "" &&
					cps[j].DefaultSNI != "" &&
					cps[i].DefaultSNI != cps[j].DefaultSNI {
					return nil, fmt.Errorf("two policies with same match criteria have conflicting default SNI: %s vs. %s",
						cps[i].DefaultSNI, cps[j].DefaultSNI)
				}
				if cps[i].FallbackSNI != "" &&
					cps[j].FallbackSNI != "" &&
					cps[i].FallbackSNI != cps[j].FallbackSNI {
					return nil, fmt.Errorf("two policies with same match criteria have conflicting fallback SNI: %s vs. %s",

View on GitHub (pinned to 50e54ee279)

Solutions

  1. Move client_auth to a site block keyed on a distinct hostname/subdomain so its SNI matcher differs
  2. Apply the same client_auth block to every site sharing the hostname
  3. Drop the plain-TLS duplicate block so a single policy covers the name

Example fix

# before
api.example.com {
  tls {
    client_auth {
      mode require_and_verify
      trusted_ca_cert_file ca.pem
    }
  }
  respond "mtls"
}
api.example.com {
  respond "no-mtls"
}
# after
api.example.com {
  tls {
    client_auth {
      mode require_and_verify
      trusted_ca_cert_file ca.pem
    }
  }
  respond "mtls"
}
Defensive patterns

Strategy: validation

Validate before calling

# client_auth must be uniform per hostname
for h, cfgs in group_clientauth_by_host(sites).items():
    assert all(c == cfgs[0] for c in cfgs), f'conflicting client_auth for {h}'

Prevention

When it happens

Trigger: One site block for a hostname using plain `tls` and another block for the same hostname (or an overlapping SNI set) using `tls { client_auth { ... } }` — after consolidation both policies remain with the same matchers but incompatible client auth settings.

Common situations: Adding mTLS to one path of a domain while another block for the same domain lacks it, duplicating a site for staging/prod with the same name but only one requiring client certs, or a global client_auth policy overlapping a site without one.

Related errors


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