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

  1. Verify the path exists and is readable by the Caddy user: ls -l /etc/caddy/leaf.pem and run with the service user
  2. Fix container mounts/volumes so the cert file is present at the configured path
  3. For the inline loader, re-encode the cert: base64 -w0 leaf.der and paste the full string
  4. 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

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

Related errors


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