caddyserver/caddy · error

two policies with same match criteria have conflicting min p

Error message

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

What it means

Two TLS connection policies with the same match criteria declare different non-empty minimum TLS protocol versions (`tls { protocols <min> [<max>] }`). The adapter refuses to pick between two minimums for the same connections.

Source

Thrown at caddyconfig/httpcaddyfile/httptype.go:1290

					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",
						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)
					}
				}

View on GitHub (pinned to 50e54ee279)

Solutions

  1. Use one identical protocols setting for all blocks sharing the hostnames
  2. Delete the duplicated site/tls block
  3. Raise the protocol floor globally via site policy consistently, or drop it to use defaults

Example fix

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

Strategy: validation

Validate before calling

for h, mins in group_protocolmin_by_host(sites).items():
    assert len({m for m in mins if m}) <= 1, f'conflicting protocol min for {h}'

Prevention

When it happens

Trigger: One site block for a hostname specifying `tls { protocols tls1.3 }` and another block for the same hostname specifying `tls { protocols tls1.2 }` — both policies reach consolidation with equal matchers but different ProtocolMin.

Common situations: Hardening one copy of a duplicated site to TLS 1.3 while the duplicate keeps tls1.2, or a hardening snippet applied to a site that already had its own protocols line.

Related errors


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