caddyserver/caddy · error

two policies with same match criteria have conflicting fallb

Error message

two policies with same match criteria have conflicting fallback SNI: %s vs. %s

What it means

Two connection policies matching the same SNI criteria set different non-empty FallbackSNI values. FallbackSNI is used when the ClientHello SNI does not match any configured name; two conflicting fallbacks for the same policy scope cannot be merged.

Source

Thrown at caddyconfig/httpcaddyfile/httptype.go:1284

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

View on GitHub (pinned to 50e54ee279)

Solutions

  1. Define fallback_sni once (global option) and remove per-site duplicates
  2. Make all fallback_sni values identical
  3. Isolate servers needing different fallbacks onto separate listeners

Example fix

# before
{
  fallback_sni a.example.com
}
example.com {
  tls {
    # conflicting fallback set here via imported snippet: b.example.com
  }
}
# after
{
  fallback_sni a.example.com
}
example.com {
  respond "ok"
}
Defensive patterns

Strategy: validation

Validate before calling

assert count_of_fallback_sni_declarations(config) <= 1

Prevention

When it happens

Trigger: `fallback_sni` declared in the global options and also (differently) via a tls policy for overlapping names on the same server, or two snippets each setting fallback_sni applied to the same site.

Common situations: Adding fallback_sni per-site after already defining it globally with a different value; importing shared snippets that both carry fallback_sni.

Related errors


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