caddyserver/caddy · error

two policies with same match criteria have conflicting max…

Error message

two policies with same match criteria have conflicting max protocol: %s vs. %s

What it means

Two connection policies with the same match criteria declare different non-empty maximum TLS protocol versions. As with ProtocolMin, two different ceilings for the same matched connections cannot be reconciled and adaptation aborts.

Solutions

  1. Unify the protocols ranges across all blocks for those hostnames
  2. Remove the duplicate block
  3. Prefer Caddy defaults unless a cap is required, and set it in exactly one place

Example fix

# before
example.com {
  tls {
    protocols tls1.2 tls1.2
  }
}
example.com {
  tls {
    protocols tls1.2 tls1.3
  }
}
# after
example.com {
  tls {
    protocols tls1.2 tls1.3
  }
}
Defensive patterns

Strategy: validation

Validate before calling

for h, maxs in group_protocolmax_by_host(sites).items():
    assert len({m for m in maxs if m}) <= 1, f'conflicting protocol max for {h}'

Prevention

When it happens

Trigger: Site blocks sharing hostnames where one caps protocols at `tls1.2` (`tls { protocols tls1.0 tls1.2 }`) and another allows `tls1.3` — both survive to consolidation with identical matchers.

Common situations: Legacy-client compat block capping max version duplicated against a modern block for the same domain; version-cap snippets imported inconsistently.

Related errors


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

Appendix: source

Thrown at caddyconfig/httpcaddyfile/httptype.go:1296

					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",
						cps[i].FallbackSNI, cps[j].FallbackSNI)
				}
				if cps[i].ProtocolMin != "" &&
					cps[j].ProtocolMin != "" &&
					cps[i].ProtocolMin != cps[j].ProtocolMin {
					return nil, fmt.Errorf("two policies with same match criteria have conflicting min protocol: %s vs. %s",
						cps[i].ProtocolMin, cps[j].ProtocolMin)
				}
				if cps[i].ProtocolMax != "" &&
					cps[j].ProtocolMax != "" &&
					cps[i].ProtocolMax != cps[j].ProtocolMax {
					return nil, fmt.Errorf("two policies with same match criteria have conflicting max protocol: %s vs. %s",
						cps[i].ProtocolMax, cps[j].ProtocolMax)
				}
				if cps[i].CertSelection != nil && cps[j].CertSelection != nil {
					// merging fields other than AnyTag is not implemented
					if !reflect.DeepEqual(cps[i].CertSelection.SerialNumber, cps[j].CertSelection.SerialNumber) ||
						!reflect.DeepEqual(cps[i].CertSelection.SubjectOrganization, cps[j].CertSelection.SubjectOrganization) ||
						cps[i].CertSelection.PublicKeyAlgorithm != cps[j].CertSelection.PublicKeyAlgorithm ||
						!reflect.DeepEqual(cps[i].CertSelection.AllTags, cps[j].CertSelection.AllTags) {
						return nil, fmt.Errorf("two policies with same match criteria have conflicting cert selections: %+v vs. %+v",
							cps[i].CertSelection, cps[j].CertSelection)
					}
				}

				// by now we've decided that we can merge the two -- we'll keep i and drop j

				if len(cps[i].ALPN) == 0 && len(cps[j].ALPN) > 0 {
					cps[i].ALPN = cps[j].ALPN
				}

View on GitHub (pinned to 50e54ee279)