{"record":{"id":"fcf01a1f25524571","repo":"caddyserver/caddy","slug":"two-policies-with-same-match-criteria-have-conflic-fcf01a","errorCode":null,"errorMessage":"two policies with same match criteria have conflicting cert selections: %+v vs. %+v","messagePattern":"two policies with same match criteria have conflicting cert selections: %\\+v vs\\. %\\+v","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"caddyconfig/httpcaddyfile/httptype.go","lineNumber":1305,"sourceCode":"\t\t\t\tif cps[i].ProtocolMin != \"\" &&\n\t\t\t\t\tcps[j].ProtocolMin != \"\" &&\n\t\t\t\t\tcps[i].ProtocolMin != cps[j].ProtocolMin {\n\t\t\t\t\treturn nil, fmt.Errorf(\"two policies with same match criteria have conflicting min protocol: %s vs. %s\",\n\t\t\t\t\t\tcps[i].ProtocolMin, cps[j].ProtocolMin)\n\t\t\t\t}\n\t\t\t\tif cps[i].ProtocolMax != \"\" &&\n\t\t\t\t\tcps[j].ProtocolMax != \"\" &&\n\t\t\t\t\tcps[i].ProtocolMax != cps[j].ProtocolMax {\n\t\t\t\t\treturn nil, fmt.Errorf(\"two policies with same match criteria have conflicting max protocol: %s vs. %s\",\n\t\t\t\t\t\tcps[i].ProtocolMax, cps[j].ProtocolMax)\n\t\t\t\t}\n\t\t\t\tif cps[i].CertSelection != nil && cps[j].CertSelection != nil {\n\t\t\t\t\t// merging fields other than AnyTag is not implemented\n\t\t\t\t\tif !reflect.DeepEqual(cps[i].CertSelection.SerialNumber, cps[j].CertSelection.SerialNumber) ||\n\t\t\t\t\t\t!reflect.DeepEqual(cps[i].CertSelection.SubjectOrganization, cps[j].CertSelection.SubjectOrganization) ||\n\t\t\t\t\t\tcps[i].CertSelection.PublicKeyAlgorithm != cps[j].CertSelection.PublicKeyAlgorithm ||\n\t\t\t\t\t\t!reflect.DeepEqual(cps[i].CertSelection.AllTags, cps[j].CertSelection.AllTags) {\n\t\t\t\t\t\treturn nil, fmt.Errorf(\"two policies with same match criteria have conflicting cert selections: %+v vs. %+v\",\n\t\t\t\t\t\t\tcps[i].CertSelection, cps[j].CertSelection)\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// by now we've decided that we can merge the two -- we'll keep i and drop j\n\n\t\t\t\tif len(cps[i].ALPN) == 0 && len(cps[j].ALPN) > 0 {\n\t\t\t\t\tcps[i].ALPN = cps[j].ALPN\n\t\t\t\t}\n\t\t\t\tif len(cps[i].CipherSuites) == 0 && len(cps[j].CipherSuites) > 0 {\n\t\t\t\t\tcps[i].CipherSuites = cps[j].CipherSuites\n\t\t\t\t}\n\t\t\t\tif cps[i].ClientAuthentication == nil && cps[j].ClientAuthentication != nil {\n\t\t\t\t\tcps[i].ClientAuthentication = cps[j].ClientAuthentication\n\t\t\t\t}\n\t\t\t\tif len(cps[i].Curves) == 0 && len(cps[j].Curves) > 0 {\n\t\t\t\t\tcps[i].Curves = cps[j].Curves\n\t\t\t\t}","sourceCodeStart":1287,"sourceCodeEnd":1323,"githubUrl":"https://github.com/caddyserver/caddy/blob/50e54ee279aa1e504fe218ca49ab6ae16c100410/caddyconfig/httpcaddyfile/httptype.go#L1287-L1323","documentation":"Two connection policies with identical match criteria both define certificate selection rules (tls { cert_selection ... }) that differ in SerialNumber, SubjectOrganization, PublicKeyAlgorithm, or AllTags. Only AnyTag merging is implemented, so any other mismatch is fatal.","triggerScenarios":"Two site blocks for the same hostname(s) each declaring a `cert_selection` block with different criteria — e.g. one selects by serial number, another by public key algorithm — leaving both policies with the same SNI matchers at consolidation time.","commonSituations":"Choosing among multiple certs for the same name with duplicated site blocks, or importing a cert-selection snippet twice with tweaked values.","solutions":["Keep a single cert_selection block per hostname set and delete duplicates","If both are needed, make every field except AnyTag identical","Move one selection to a distinct hostname so the SNI matchers differ"],"exampleFix":"# before\nexample.com {\n  tls {\n    cert_selection {\n      public_key_algorithm rsa\n    }\n  }\n}\nexample.com {\n  tls {\n    cert_selection {\n      public_key_algorithm ecdsa\n    }\n  }\n}\n# after\nexample.com {\n  tls {\n    cert_selection {\n      public_key_algorithm rsa\n    }\n  }\n}","handlingStrategy":"validation","validationCode":"for h, sels in group_certselection_by_host(sites).items():\n    assert len({canonical(s) for s in sels}) <= 1, f'conflicting cert_selection for {h}'","typeGuard":null,"tryCatchPattern":null,"preventionTips":["One cert_selection per hostname","Use tags (AnyTag) where possible — it is the only mergeable field","Keep multi-cert domains on distinct subdomains per selection"],"tags":["caddy","tls","certificates","caddyfile","conflict"],"backgroundTag":null,"analyzedSha":"50e54ee279aa1e504fe218ca49ab6ae16c100410","analyzedAt":"2026-08-15T09:20:21.641Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}