caddyserver/caddy · error
could not load leaf certificates: %s
Error message
could not load leaf certificates: %s
What it means
After successfully parsing leaf cert loaders, LeafCertClientAuth.Provision calls LoadLeafCertificates() on each; any failure (missing file, unreadable file, bad base64 in inline loader) is wrapped as 'could not load leaf certificates'. The load itself failed, as opposed to module parsing (previous error).
Source
Thrown at modules/caddytls/connpolicy.go:981
}
func (l *LeafCertClientAuth) Provision(ctx caddy.Context) error {
if l.LeafCertificateLoadersRaw == nil {
return nil
}
val, err := ctx.LoadModule(l, "LeafCertificateLoadersRaw")
if err != nil {
return fmt.Errorf("could not parse leaf certificates loaders: %s", err.Error())
}
trustedLeafCertloaders := []LeafCertificateLoader{}
for _, loader := range val.([]any) {
trustedLeafCertloaders = append(trustedLeafCertloaders, loader.(LeafCertificateLoader))
}
trustedLeafCertificates := []*x509.Certificate{}
for _, loader := range trustedLeafCertloaders {
certs, err := loader.LoadLeafCertificates()
if err != nil {
return fmt.Errorf("could not load leaf certificates: %s", err.Error())
}
trustedLeafCertificates = append(trustedLeafCertificates, certs...)
}
l.trustedLeafCerts = trustedLeafCertificates
return nil
}
// UnmarshalCaddyfile implements caddyfile.Unmarshaler.
func (l *LeafCertClientAuth) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
d.NextArg()
// accommodate the use of one-liners
if d.CountRemainingArgs() > 1 {
d.NextArg()
modName := d.Val()
mod, err := caddyfile.UnmarshalModule(d, "tls.leaf_cert_loader."+modName)
if err != nil {
return d.WrapErr(err)View on GitHub (pinned to 50e54ee279)
Solutions
- Verify the path exists and is readable by the Caddy user: ls -l /etc/caddy/leaf.pem and run with the service user
- Fix container mounts/volumes so the cert file is present at the configured path
- For the inline loader, re-encode the cert: base64 -w0 leaf.der and paste the full string
- Check the underlying error text after the colon — it states file or decode specifics
Example fix
# before
verifier leaf {
leaf_cert_file /etc/caddy/leaf.pem
}
# after (ensure the file exists; or inline it)
verifier leaf {
leaf_cert inline MIIB...==
} Defensive patterns
Strategy: validation
Validate before calling
// Pre-flight every file referenced by leaf_cert_file
func checkLeafCertFiles(paths ...string) error {
for _, p := range paths {
b, err := os.ReadFile(p)
if err != nil {
return fmt.Errorf("leaf cert %s: %w", p, err)
}
if _, rest := pem.Decode(b); rest == nil && len(b) > 0 {
if _, err := x509.ParseCertificate(b); err != nil {
return fmt.Errorf("leaf cert %s: not PEM or DER cert", p)
}
}
}
return nil
} Prevention
- Mount cert files read-only into containers and health-check their presence in entrypoint
- Set explicit file ownership for the caddy service user
- Smoke-test with 'caddy validate' plus an mTLS request after every deploy
When it happens
Trigger: leaf_cert_file pointing to a nonexistent path or a path the Caddy process cannot read; leaf_cert inline with base64 that does not decode to a certificate; file containing something other than certificate PEM/DER depending on loader implementation.
Common situations: Container deployments where the cert file is not mounted into the container; file permissions (root-owned file, Caddy running as non-root); deploying config before provisioning certs; path typos or missing directory.
Understand the failure class
- SSL/TLS and certificate errors — how TLS handshakes and certificate validation fail.
Related errors
- no CERTIFICATE pem block found in %s
- two policies with same match criteria have conflicting clien
- loading trusted root CA's PEM file: %s: %v
- parsing certificate at index %d: %v
- reading %s: %v
AI-assisted analysis of caddyserver/caddy@50e54ee279 (2026-08-15).
Data as JSON: /api/errors/b941137c34763447.
Report an issue: GitHub.