caddyserver/caddy · critical
saving root key: %v
Error message
saving root key: %v
What it means
The final step of genRoot stores the root key PEM at storageKeyRootKey. A storage write failure here aborts CA setup with 'saving root key'. Note the root cert may already be stored at this point, leaving intentionally-recoverable-but-inconsistent assets (see the TODO in this file about all-or-none assets), so the next start will find a cert without a key unless cleaned up.
Source
Thrown at modules/caddypki/ca.go:339
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)
if err != nil {
return nil, nil, fmt.Errorf("encoding root certificate: %v", err)
}
err = ca.storage.Store(ca.ctx, ca.storageKeyRootCert(), rootCertPEM)
if err != nil {
return nil, nil, fmt.Errorf("saving root certificate: %v", err)
}
rootKeyPEM, err := certmagic.PEMEncodePrivateKey(rootKey)
if err != nil {
return nil, nil, fmt.Errorf("encoding root key: %v", err)
}
err = ca.storage.Store(ca.ctx, ca.storageKeyRootKey(), rootKeyPEM)
if err != nil {
return nil, nil, fmt.Errorf("saving root key: %v", err)
}
return rootCert, rootKey, nil
}
func (ca CA) loadOrGenIntermediate(rootCert *x509.Certificate, rootKey crypto.Signer) (interCertChain []*x509.Certificate, interKey crypto.Signer, err error) {
var interCert *x509.Certificate
interCertPEM, err := ca.storage.Load(ca.ctx, ca.storageKeyIntermediateCert())
if err != nil {
if !errors.Is(err, fs.ErrNotExist) {
return nil, nil, fmt.Errorf("loading intermediate cert: %v", err)
}
// TODO: should we require that all or none of the assets are required before overwriting anything?
interCert, interKey, err = ca.genIntermediate(rootCert, rootKey)
if err != nil {
return nil, nil, fmt.Errorf("generating new intermediate cert: %v", err)
}View on GitHub (pinned to 50e54ee279)
Solutions
- Fix storage writability/capacity, then delete the partially provisioned CA directory in storage (cert without key) and restart so root generation re-runs atomically.
- Monitor storage free space so provisioning cannot run out mid-write.
- For custom backends, ensure writes are atomic and idempotent across restarts.
- If assets must be kept, manually place a matching key.pem; otherwise regenerating is safer.
Defensive patterns
Strategy: retry
Validate before calling
// After provisioning attempts, detect partial state (cert without key)
_, e1 := os.Stat(caDir + "/root.crt")
_, e2 := os.Stat(caDir + "/key.pem")
if (e1 == nil) != (e2 == nil) { cleanupPartialCA(caDir) } Type guard
func caStorageConsistent(dir string) bool {
_, c := os.Stat(filepath.Join(dir, "root.crt"))
_, k := os.Stat(filepath.Join(dir, "key.pem"))
return (c == nil) == (k == nil)
} Try / catch
if strings.Contains(err.Error(), "saving root key") {
// free space / fix backend, delete partial CA dir, restart to regenerate cleanly
} Prevention
- Watch disk space alerts below a threshold that covers asset writes.
- On provisioning failure, always clean partial CA assets before retrying.
- Prefer storage backends with atomic writes for PKI data.
When it happens
Trigger: storage.Store(key) fails after Store(cert) succeeded: disk fills up mid-provisioning, backend drops between two writes, permissions change. First-boot-of-CA path only.
Common situations: Disk-full or quota-hit exactly during provisioning; flaky network storage; partially provisioned CA left behind after a crash, causing confusing follow-up errors (e.g. 586/587) on next start.
Related errors
- loading root key: %v
- loading signing key: %v
- loading intermediate key: %v
- failed to get root and intermediate cert for CA %s: %v
- failed to provision CA %s, %w
AI-assisted analysis of caddyserver/caddy@50e54ee279 (2026-08-15).
Data as JSON: /api/errors/9c3c3c0eb61c1a5c.
Report an issue: GitHub.