caddyserver/caddy · error
two policies with same match criteria have conflicting max p
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.
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)
Solutions
- Unify the protocols ranges across all blocks for those hostnames
- Remove the duplicate block
- 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
- Avoid version caps unless a legacy client needs them; then set in one place
- Deduplicate site blocks
- Validate merged configs with caddy adapt
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
- two policies with same match criteria have conflicting min p
- server listening on %v is HTTP, but attempts to configure TL
- two policies with same match criteria have conflicting ALPN:
- two policies with same match criteria have conflicting ciphe
- two policies with same match criteria have conflicting curve
AI-assisted analysis of caddyserver/caddy@50e54ee279 (2026-08-15).
Data as JSON: /api/errors/c8b74cc4c6ea85cd.
Report an issue: GitHub.