caddyserver/caddy · error

loading certificates: %v

Error message

loading certificates: %v

What it means

After all certificate loader modules are provisioned, each loader's LoadCertificates() is called to actually read the certificates from disk (or elsewhere). This error wraps a loader failure: files that do not exist, are unreadable due to permissions, or are not parseable as PEM-encoded certificate/key pairs.

Source

Thrown at modules/caddytls/tls.go:266

	// manual/static (unmanaged) certificates - we do this in
	// provision so that other apps (such as http) can know which
	// certificates have been manually loaded, and also so that
	// commands like validate can be a better test
	certCacheMu.RLock()
	magic := certmagic.New(certCache, certmagic.Config{
		Storage: ctx.Storage(),
		Logger:  t.logger,
		OnEvent: t.onEvent,
		OCSP: certmagic.OCSPConfig{
			DisableStapling: t.DisableOCSPStapling,
		},
		DisableStorageCheck: t.DisableStorageCheck,
	})
	certCacheMu.RUnlock()
	for _, loader := range t.certificateLoaders {
		certs, err := loader.LoadCertificates()
		if err != nil {
			return fmt.Errorf("loading certificates: %v", err)
		}
		for _, cert := range certs {
			hash, err := magic.CacheUnmanagedTLSCertificate(ctx, cert.Certificate, cert.Tags)
			if err != nil {
				return fmt.Errorf("caching unmanaged certificate: %v", err)
			}
			t.loaded[hash] = ""
		}
	}

	// on-demand permission module
	if t.Automation != nil && t.Automation.OnDemand != nil && t.Automation.OnDemand.PermissionRaw != nil {
		if t.Automation.OnDemand.Ask != "" {
			return fmt.Errorf("on-demand TLS config conflict: both 'ask' endpoint and a 'permission' module are specified; 'ask' is deprecated, so use only the permission module")
		}
		val, err := ctx.LoadModule(t.Automation.OnDemand, "PermissionRaw")
		if err != nil {
			return fmt.Errorf("loading on-demand TLS permission module: %v", err)

View on GitHub (pinned to 50e54ee279)

Solutions

  1. Read the wrapped error: it comes from the specific loader and usually names the offending file
  2. Verify paths exist and are readable by the Caddy process user (check mount points in containers)
  3. Validate PEM format: 'openssl x509 -in cert.pem -noout' and 'openssl pkey -in key.pem -noout' must succeed
  4. Confirm cert/key pair matches: compare modulus or use 'openssl x509 -noout -modulus' vs 'openssl rsa -noout -modulus'

Example fix

# before
load_files: certificate=/etc/cert/example.pem (file missing)
# after: point to the real path and verify
$ openssl x509 -in /etc/ssl/example.com/cert.pem -noout
{"load_files": [{"certificate": "/etc/ssl/example.com/cert.pem", "key": "/etc/ssl/example.com/key.pem"}]}
Defensive patterns

Strategy: validation

Validate before calling

for f in /certs/cert.pem /certs/key.pem; do
  test -r "$f" || echo "unreadable: $f"
  openssl x509 -in "$f" -noout >/dev/null 2>&1 || openssl pkey -in "$f" -noout >/dev/null 2>&1 || echo "not PEM: $f"
done

Prevention

When it happens

Trigger: load_files pointing to a certificate or key path that does not exist or has wrong ownership/mode; PEM blocks that are truncated, binary (DER instead of PEM), or whose key does not match the certificate; load_folders containing corrupt files.

Common situations: Cert files mounted into a container at a different path than configured; permissions changed after a system update; cert/key regenerated by another tool in DER format; copy-pasting certs introducing whitespace/truncation.

Understand the failure class

Related errors


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