caddyserver/caddy · error

recombining SNI matchers: %v

Error message

recombining SNI matchers: %v

What it means

While consolidating connection policies, two policies with equal settings and positionally compatible SNI matchers were being merged by concatenating their MatchServerName lists; re-marshaling the combined list to JSON failed. This is essentially an internal serialization failure of data the adapter itself produced.

Source

Thrown at caddyconfig/httpcaddyfile/httptype.go:1236

			// as a special case, if there are adjacent TLS conn policies that are identical except
			// by their matchers, and the matchers are specifically just ServerName ("sni") matchers
			// (by far the most common), we can combine them into a single policy
			if i == j-1 && len(cps[i].MatchersRaw) == 1 && len(cps[j].MatchersRaw) == 1 {
				if iSNIMatcherJSON, ok := cps[i].MatchersRaw["sni"]; ok {
					if jSNIMatcherJSON, ok := cps[j].MatchersRaw["sni"]; ok {
						// position of policies and the matcher criteria check out; if settings are
						// the same, then we can combine the policies; we have to unmarshal and
						// remarshal the matchers though
						if cps[i].SettingsEqual(*cps[j]) {
							var iSNIMatcher caddytls.MatchServerName
							if err := json.Unmarshal(iSNIMatcherJSON, &iSNIMatcher); err == nil {
								var jSNIMatcher caddytls.MatchServerName
								if err := json.Unmarshal(jSNIMatcherJSON, &jSNIMatcher); err == nil {
									iSNIMatcher = append(iSNIMatcher, jSNIMatcher...)
									cps[i].MatchersRaw["sni"], err = json.Marshal(iSNIMatcher)
									if err != nil {
										return nil, fmt.Errorf("recombining SNI matchers: %v", err)
									}
									cps = slices.Delete(cps, j, j+1)
									i--
									break
								}
							}
						}
					}
				}
			}

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

View on GitHub (pinned to 50e54ee279)

Solutions

  1. If you use a custom adapter/plugin, ensure `sni` matchers in MatchersRaw are valid JSON arrays of strings
  2. Update Caddy — internal marshaling bugs of this kind get fixed upstream
  3. Reproduce with `caddy adapt --pretty` on a minimal config to identify which plugin mangles the matchers
Defensive patterns

Strategy: try-catch

Try / catch

if err := json.Marshal(iSNIMatcher); err != nil {
    // internal path: report upstream with config that reproduces it
    return nil, fmt.Errorf("recombining SNI matchers: %w", err)
}

Prevention

When it happens

Trigger: consolidateConnPolicies finds cps[i] and cps[j] with SettingsEqual and mergeable SNI matchers, unmarshals both `sni` matcher JSON blobs into caddytls.MatchServerName, appends them, and json.Marshal of the combined slice errors — in practice only reachable if the MatchersRaw['sni'] payload is not what the adapter expects (e.g. third-party code injected malformed matcher JSON).

Common situations: Almost never seen with a stock Caddyfile; can occur when a plugin or custom adapter pre-populates or mutates connection policy MatchersRaw with invalid SNI matcher JSON before consolidation runs.

Related errors


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