caddyserver/caddy · error
client leaf certificate failed validation
Error message
client leaf certificate failed validation
What it means
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.
Source
Thrown at modules/caddytls/connpolicy.go:1045
}
return nil
}
func (l LeafCertClientAuth) VerifyClientCertificate(rawCerts [][]byte, _ [][]*x509.Certificate) error {
if len(rawCerts) == 0 {
return fmt.Errorf("no client certificate provided")
}
remoteLeafCert, err := x509.ParseCertificate(rawCerts[0])
if err != nil {
return fmt.Errorf("can't parse the given certificate: %s", err.Error())
}
if slices.ContainsFunc(l.trustedLeafCerts, remoteLeafCert.Equal) {
return nil
}
return fmt.Errorf("client leaf certificate failed validation")
}
// PublicKeyAlgorithm is a JSON-unmarshalable wrapper type.
type PublicKeyAlgorithm x509.PublicKeyAlgorithm
// UnmarshalJSON satisfies json.Unmarshaler.
func (a *PublicKeyAlgorithm) UnmarshalJSON(b []byte) error {
algoStr := strings.ToLower(strings.Trim(string(b), `"`))
algo, ok := publicKeyAlgorithms[algoStr]
if !ok {
return fmt.Errorf("unrecognized public key algorithm: %s (expected one of %v)",
algoStr, publicKeyAlgorithms)
}
*a = PublicKeyAlgorithm(algo)
return nil
}
// ConnectionMatcher is a type which matches TLS handshakes.View on GitHub (pinned to 50e54ee279)
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
Example fix
# before: pinning an intermediate (handshake always fails)
verifier leaf {
leaf_cert_file /etc/caddy/intermediate.pem
}
# after: pin the exact client leaf, or switch to CA verification
verifier leaf {
leaf_cert_file /etc/caddy/client-leaf.pem
} Defensive patterns
Strategy: validation
Validate before calling
// Server-side: before reload, confirm every trusted leaf fingerprint matches intent
func fingerprint(path string) (string, error) {
b, _ := os.ReadFile(path)
blk, _ := pem.Decode(b)
c, err := x509.ParseCertificate(blk.Bytes)
if err != nil {
return "", err
}
return fmt.Sprintf("%X", sha256.Sum256(c.Raw)), nil
}
// compare against the client cert actually issued today Prevention
- 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
When it happens
Trigger: 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).
Common situations: 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.
Understand the failure class
- SSL/TLS and certificate errors — how TLS handshakes and certificate validation fail.
Related errors
- no client certificate provided
- two policies with same match criteria have conflicting clien
- client_certificate_file specified without client_certificate
- client_certificate_key_file specified without client_certifi
- can't parse the given certificate: %s
AI-assisted analysis of caddyserver/caddy@50e54ee279 (2026-08-15).
Data as JSON: /api/errors/a6e29aee9bbe4fed.
Report an issue: GitHub.