{"record":{"id":"8954e72ec258607b","repo":"caddyserver/caddy","slug":"saving-root-certificate-v","errorCode":null,"errorMessage":"saving root certificate: %v","messagePattern":"saving root certificate: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"modules/caddypki/ca.go","lineNumber":331,"sourceCode":"\t}\n\n\treturn rootCert, rootKey, nil\n}\n\nfunc (ca CA) genRoot() (rootCert *x509.Certificate, rootKey crypto.Signer, err error) {\n\trepl := ca.newReplacer()\n\n\trootCert, rootKey, err = generateRoot(repl.ReplaceAll(ca.RootCommonName, \"\"))\n\tif err != nil {\n\t\treturn nil, nil, fmt.Errorf(\"generating CA root: %v\", err)\n\t}\n\trootCertPEM, err := pemEncodeCert(rootCert.Raw)\n\tif err != nil {\n\t\treturn nil, nil, fmt.Errorf(\"encoding root certificate: %v\", err)\n\t}\n\terr = ca.storage.Store(ca.ctx, ca.storageKeyRootCert(), rootCertPEM)\n\tif err != nil {\n\t\treturn nil, nil, fmt.Errorf(\"saving root certificate: %v\", err)\n\t}\n\trootKeyPEM, err := certmagic.PEMEncodePrivateKey(rootKey)\n\tif err != nil {\n\t\treturn nil, nil, fmt.Errorf(\"encoding root key: %v\", err)\n\t}\n\terr = ca.storage.Store(ca.ctx, ca.storageKeyRootKey(), rootKeyPEM)\n\tif err != nil {\n\t\treturn nil, nil, fmt.Errorf(\"saving root key: %v\", err)\n\t}\n\n\treturn rootCert, rootKey, nil\n}\n\nfunc (ca CA) loadOrGenIntermediate(rootCert *x509.Certificate, rootKey crypto.Signer) (interCertChain []*x509.Certificate, interKey crypto.Signer, err error) {\n\tvar interCert *x509.Certificate\n\tinterCertPEM, err := ca.storage.Load(ca.ctx, ca.storageKeyIntermediateCert())\n\tif err != nil {\n\t\tif !errors.Is(err, fs.ErrNotExist) {","sourceCodeStart":313,"sourceCodeEnd":349,"githubUrl":"https://github.com/caddyserver/caddy/blob/50e54ee279aa1e504fe218ca49ab6ae16c100410/modules/caddypki/ca.go#L313-L349","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","solutions":["Grant write access to the storage location for the Caddy process (chown/chmod the data dir or bucket prefix) and restart.","Free disk space / fix backend quotas, then delete any partially written assets and restart so generation re-runs cleanly.","If using custom storage, test it directly (caddy storage export/import) to confirm read/write works.","Point storage to a known-writable location via the storage global option."],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// Pre-start: storage must be writable\nprobe := filepath.Join(dataDir, \".write-probe\")\nif err := os.WriteFile(probe, []byte(\"x\"), 0o600); err != nil { log.Fatalf(\"storage not writable: %v\", err) }\nos.Remove(probe)","typeGuard":null,"tryCatchPattern":"if strings.Contains(err.Error(), \"saving root certificate\") {\n    // fix writability/space, remove partial assets, retry provisioning\n}","preventionTips":["Health-check storage writability before first provisioning.","Size disk quotas with PKI assets and logs in mind.","For object-storage backends, verify PutObject policy in pre-deploy checks."],"tags":["pki","storage","io","root-cert"],"backgroundTag":null,"analyzedSha":"50e54ee279aa1e504fe218ca49ab6ae16c100410","analyzedAt":"2026-08-15T09:20:21.641Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}