{"record":{"id":"a6e29aee9bbe4fed","repo":"caddyserver/caddy","slug":"client-leaf-certificate-failed-validation","errorCode":null,"errorMessage":"client leaf certificate failed validation","messagePattern":"client leaf certificate failed validation","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"modules/caddytls/connpolicy.go","lineNumber":1045,"sourceCode":"\t}\n\treturn nil\n}\n\nfunc (l LeafCertClientAuth) VerifyClientCertificate(rawCerts [][]byte, _ [][]*x509.Certificate) error {\n\tif len(rawCerts) == 0 {\n\t\treturn fmt.Errorf(\"no client certificate provided\")\n\t}\n\n\tremoteLeafCert, err := x509.ParseCertificate(rawCerts[0])\n\tif err != nil {\n\t\treturn fmt.Errorf(\"can't parse the given certificate: %s\", err.Error())\n\t}\n\n\tif slices.ContainsFunc(l.trustedLeafCerts, remoteLeafCert.Equal) {\n\t\treturn nil\n\t}\n\n\treturn fmt.Errorf(\"client leaf certificate failed validation\")\n}\n\n// PublicKeyAlgorithm is a JSON-unmarshalable wrapper type.\ntype PublicKeyAlgorithm x509.PublicKeyAlgorithm\n\n// UnmarshalJSON satisfies json.Unmarshaler.\nfunc (a *PublicKeyAlgorithm) UnmarshalJSON(b []byte) error {\n\talgoStr := strings.ToLower(strings.Trim(string(b), `\"`))\n\talgo, ok := publicKeyAlgorithms[algoStr]\n\tif !ok {\n\t\treturn fmt.Errorf(\"unrecognized public key algorithm: %s (expected one of %v)\",\n\t\t\talgoStr, publicKeyAlgorithms)\n\t}\n\t*a = PublicKeyAlgorithm(algo)\n\treturn nil\n}\n\n// ConnectionMatcher is a type which matches TLS handshakes.","sourceCodeStart":1027,"sourceCodeEnd":1063,"githubUrl":"https://github.com/caddyserver/caddy/blob/50e54ee279aa1e504fe218ca49ab6ae16c100410/modules/caddytls/connpolicy.go#L1027-L1063","documentation":"The leaf verifier parsed the client's certificate successfully but found no exact match (x509.Certificate.Equal) among the configured trustedLeafCerts, so the mTLS handshake is rejected: this is the expected 'untrusted client' outcome of leaf-pinned client authentication.","triggerScenarios":"Client presents a certificate that is valid X.509 but not byte-for-byte one of the loaded trust anchors; client cert was renewed/reissued and the server still pins the old one; wrong trust file loaded (dev cert in prod).","commonSituations":"Certificate rotation without updating the server's trusted leaf list; environments where clients have many certs and the server pins one; accidental trust of an intermediate instead of the actual leaf.","solutions":["Confirm which cert the client sends (openssl x509 -in client.pem -noout -fingerprint -sha256) and compare with the server's loaded leaf(s)","If the client cert was rotated, update the server's leaf_cert_file/inline list and reload Caddy (config reload picks up new files)","Prefer CA-based verification (trusted_ca_cert_file / ca module) over leaf pinning unless you specifically need pinning — it survives client cert renewal","Check you pinned the leaf certificate, not the CA/intermediate"],"exampleFix":"# before: pinning an intermediate (handshake always fails)\nverifier leaf {\n  leaf_cert_file /etc/caddy/intermediate.pem\n}\n\n# after: pin the exact client leaf, or switch to CA verification\nverifier leaf {\n  leaf_cert_file /etc/caddy/client-leaf.pem\n}","handlingStrategy":"validation","validationCode":"// Server-side: before reload, confirm every trusted leaf fingerprint matches intent\nfunc fingerprint(path string) (string, error) {\n\tb, _ := os.ReadFile(path)\n\tblk, _ := pem.Decode(b)\n\tc, err := x509.ParseCertificate(blk.Bytes)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn fmt.Sprintf(\"%X\", sha256.Sum256(c.Raw)), nil\n}\n// compare against the client cert actually issued today","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Prefer CA-based verification; use leaf pinning only when pinning is the requirement","On client cert rotation, update the server trust list in the same change and reload Caddy","Alert on 'client leaf certificate failed validation' spikes — they usually mean a stale pin"],"tags":["tls","mtls","client-cert","pinning"],"backgroundTag":null,"analyzedSha":"50e54ee279aa1e504fe218ca49ab6ae16c100410","analyzedAt":"2026-08-15T09:20:21.641Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}