caddyserver/caddy · critical

loading root key: %v

Error message

loading root key: %v

What it means

After successfully loading a root cert from storage, Caddy loads the root's private key (storageKeyRootKey, e.g. key.pem). Any storage error here — the file/block is missing in a way not surfaced as fs.ErrNotExist handling for this branch, or the backend fails — is wrapped as 'loading root key'. Without the key the root cannot sign a new intermediate.

Source

Thrown at modules/caddypki/ca.go:307

		}

		// TODO: should we require that all or none of the assets are required before overwriting anything?
		rootCert, rootKey, err = ca.genRoot()
		if err != nil {
			return nil, nil, fmt.Errorf("generating root: %v", err)
		}
	}

	if rootCert == nil {
		rootCert, err = pemDecodeCertificate(rootCertPEM)
		if err != nil {
			return nil, nil, fmt.Errorf("parsing root certificate PEM: %v", err)
		}
	}
	if rootKey == nil {
		rootKeyPEM, err := ca.storage.Load(ca.ctx, ca.storageKeyRootKey())
		if err != nil {
			return nil, nil, fmt.Errorf("loading root key: %v", err)
		}
		rootKey, err = certmagic.PEMDecodePrivateKey(rootKeyPEM)
		if err != nil {
			return nil, nil, fmt.Errorf("decoding root key: %v", err)
		}
	}

	return rootCert, rootKey, nil
}

func (ca CA) genRoot() (rootCert *x509.Certificate, rootKey crypto.Signer, err error) {
	repl := ca.newReplacer()

	rootCert, rootKey, err = generateRoot(repl.ReplaceAll(ca.RootCommonName, ""))
	if err != nil {
		return nil, nil, fmt.Errorf("generating CA root: %v", err)
	}
	rootCertPEM, err := pemEncodeCert(rootCert.Raw)

View on GitHub (pinned to 50e54ee279)

Solutions

  1. Restore the root key object at the expected storage key (pair it with the existing root.crt), e.g. copy key.pem back to storage/caddy/pki/<id>/ca/.
  2. If the key is lost, the root is unusable: delete that CA's storage assets and let Caddy generate a new root+intermediate, then redistribute trust (caddy trust) and reissue leaf certs (they are typically short-lived).
  3. Fix permissions on the storage directory so the process can read both CA files.
  4. For multi-instance deployments, verify all nodes see the same, complete storage contents.
Defensive patterns

Strategy: try-catch

Validate before calling

// Pre-start integrity check: cert present => key must be present too
_, certErr := os.Stat(caDir + "/root.crt")
_, keyErr := os.Stat(caDir + "/key.pem")
if certErr == nil && keyErr != nil { log.Fatal("partial CA state: root cert without key") }

Type guard

func caAssetsComplete(dir string) bool {
    for _, f := range []string{"root.crt", "key.pem"} {
        if _, err := os.Stat(filepath.Join(dir, f)); err != nil { return false }
    }
    return true
}

Try / catch

if err != nil && strings.Contains(err.Error(), "loading root key") {
    // decide: restore key from backup, or wipe CA dir to regenerate + re-trust
}
return err

Prevention

When it happens

Trigger: storage.Load for the root key returns an error: the key object was deleted while the cert remains (partial state), permission/ownership mismatch on key.pem, or a custom storage backend error. Happens during loadOrGenRoot after the cert loaded fine.

Common situations: Someone removed key.pem 'for security' leaving root.crt; backup/restore that skipped dot/PEM key files; storage backend partial outage returning errors for one object; multi-node setups with inconsistent replicated storage.

Related errors


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