caddyserver/caddy · critical

saving root certificate: %v

Error message

saving root certificate: %v

What it means

genRoot persists the freshly generated root certificate with storage.Store at storageKeyRootCert. Any storage write failure — permissions, disk full, backend outage — is wrapped as 'saving root certificate'. The root exists in memory but not durably, so Caddy refuses to continue rather than risk a different root next start.

Source

Thrown at modules/caddypki/ca.go:331

	}

	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)
	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) {

View on GitHub (pinned to 50e54ee279)

Solutions

  1. Grant write access to the storage location for the Caddy process (chown/chmod the data dir or bucket prefix) and restart.
  2. Free disk space / fix backend quotas, then delete any partially written assets and restart so generation re-runs cleanly.
  3. If using custom storage, test it directly (caddy storage export/import) to confirm read/write works.
  4. Point storage to a known-writable location via the storage global option.
Defensive patterns

Strategy: retry

Validate before calling

// Pre-start: storage must be writable
probe := filepath.Join(dataDir, ".write-probe")
if err := os.WriteFile(probe, []byte("x"), 0o600); err != nil { log.Fatalf("storage not writable: %v", err) }
os.Remove(probe)

Try / catch

if strings.Contains(err.Error(), "saving root certificate") {
    // fix writability/space, remove partial assets, retry provisioning
}

Prevention

When it happens

Trigger: storage.Store fails: read-only or full filesystem for the data directory, custom storage backend (Redis/S3/consul) rejecting the write, or ownership mismatch on the storage tree. Runs only when a new root was just generated (first boot of a CA id).

Common situations: Docker bind-mount data dirs owned by root while Caddy runs as another user; disk-full nodes; S3 bucket policy denying PutObject; first provisioning racing a storage maintenance window.

Understand the failure class

Related errors


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