caddyserver/caddy · error

error loading cert '%s' from storage: %s

Error message

error loading cert '%s' from storage: %s

What it means

A storage-backed trust pool could not Load() one of the configured PEM keys from its storage backend. The wrapped error indicates whether the key was not found, was unreadable, or the backend failed.

Source

Thrown at modules/caddytls/capools.go:429

		}
		cmStorage, err := val.(caddy.StorageConverter).CertMagicStorage()
		if err != nil {
			return fmt.Errorf("creating storage configuration: %v", err)
		}
		ca.storage = cmStorage
	}
	if ca.storage == nil {
		ca.storage = ctx.Storage()
	}
	if len(ca.PEMKeys) == 0 {
		return fmt.Errorf("no PEM keys specified")
	}
	caPool := x509.NewCertPool()
	var certs []*x509.Certificate
	for _, caID := range ca.PEMKeys {
		bs, err := ca.storage.Load(ctx, caID)
		if err != nil {
			return fmt.Errorf("error loading cert '%s' from storage: %s", caID, err)
		}
		// Parse PEM to extract certificates
		pemData := bs
		for len(pemData) > 0 {
			var block *pem.Block
			block, pemData = pem.Decode(pemData)
			if block == nil {
				break
			}
			if block.Type != "CERTIFICATE" {
				continue
			}
			cert, err := x509.ParseCertificate(block.Bytes)
			if err != nil {
				return fmt.Errorf("parsing certificate '%s': %v", caID, err)
			}
			caPool.AddCert(cert)
			certs = append(certs, cert)

View on GitHub (pinned to 50e54ee279)

Solutions

  1. Verify each configured key exists in the actual storage backend being used (note: with a custom storage block, paths are resolved by that backend, not the local filesystem).
  2. Upload/copy the PEM bundles to the storage keys referenced by the pool.
  3. Check the wrapped error message for the backend-specific cause (not found vs. connection refused vs. permission denied) and fix accordingly.

Example fix

# before
trust_pool storage {
  trusted_ca_certs_pem ca-roots.pem   # not present in storage root
}

# after
# ensure the object exists: e.g. file_system storage root /var/lib/caddy
#   /var/lib/caddy/ca-roots.pem
trust_pool storage {
  trusted_ca_certs_pem ca-roots.pem
}
Defensive patterns

Strategy: validation

Validate before calling

// pre-check keys exist in a file_system-backed storage before reload
import "os"

func keysExist(root string, keys []string) error {
	for _, k := range keys {
		if _, err := os.Stat(filepath.Join(root, k)); err != nil {
			return fmt.Errorf("missing storage key %s: %w", k, err)
		}
	}
	return nil
}

Try / catch

// reload pipeline: fail the deploy on load errors instead of leaving config half-applied
if err := applyConfig(newCfg); err != nil {
	log.Printf("config apply failed (keeping previous config): %v", err)
	return err
}

Prevention

When it happens

Trigger: ca.storage.Load(ctx, caID) returning an error — the PEM file/blob does not exist in the configured storage, permissions deny reads, or the remote storage backend errored.

Common situations: Pointing trusted_ca_certs_pem at paths relative to a different storage root; certificates never uploaded to shared storage in multi-node setups; storage bucket/prefix misconfiguration; file permissions.

Related errors


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