{"record":{"id":"7faa3010973f6982","repo":"caddyserver/caddy","slug":"intermediate-certificate-lifetime-must-be-less-tha","errorCode":null,"errorMessage":"intermediate certificate lifetime must be less than actual root certificate lifetime (%s)","messagePattern":"intermediate certificate lifetime must be less than actual root certificate lifetime \\((.+?)\\)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"modules/caddypki/ca.go","lineNumber":172,"sourceCode":"\t\trootCertChain, rootKey, err = ca.Root.Load()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\trootCert = rootCertChain[0]\n\t} else {\n\t\tca.rootCertPath = \"storage:\" + ca.storageKeyRootCert()\n\t\trootCert, rootKey, err = ca.loadOrGenRoot()\n\t}\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif ca.Intermediate != nil {\n\t\tinterCertChain, interKey, err = ca.Intermediate.Load()\n\t} else {\n\t\tactualRootLifetime := time.Until(rootCert.NotAfter)\n\t\tif time.Duration(ca.IntermediateLifetime) >= actualRootLifetime {\n\t\t\treturn fmt.Errorf(\"intermediate certificate lifetime must be less than actual root certificate lifetime (%s)\", actualRootLifetime)\n\t\t}\n\n\t\tinterCertChain, interKey, err = ca.loadOrGenIntermediate(rootCert, rootKey)\n\t}\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tca.mu.Lock()\n\tca.root, ca.interChain, ca.interKey = rootCert, interCertChain, interKey\n\tca.mu.Unlock()\n\n\treturn nil\n}\n\n// RootCertificate returns the CA's root certificate (public key).\nfunc (ca CA) RootCertificate() *x509.Certificate {\n\tca.mu.RLock()","sourceCodeStart":154,"sourceCodeEnd":190,"githubUrl":"https://github.com/caddyserver/caddy/blob/50e54ee279aa1e504fe218ca49ab6ae16c100410/modules/caddypki/ca.go#L154-L190","documentation":"Caddy's PKI app validates that a generated intermediate certificate's lifetime is strictly shorter than the time remaining on the root certificate it will be signed by. If IntermediateLifetime (a caddy.Duration, e.g. in the pki app config) is >= time.Until(rootCert.NotAfter), CA initialization aborts. This prevents issuing an intermediate that outlives its issuer, which clients would reject.","triggerScenarios":"The root certificate was imported or has already run part of its life, and the configured intermediate_lifetime equals or exceeds the root's remaining validity. For example a root with 10 days left and intermediate_lifetime 144h (default 7d is fine, but explicit 12h*20 or 24h*10 = 240h would not be). Reproduce: pki { ca { root { cert ...key... } intermediate_lifetime huge } } then start Caddy.","commonSituations":"Using a short-lived external root cert (e.g. from an internal corporate CA) while keeping the default or a large intermediate_lifetime; importing an old root whose NotAfter is close; downgrading root lifetime to 1h in tests while leaving intermediate_lifetime at 24h+.","solutions":["Set intermediate_lifetime in the pki CA config to a value clearly below the root's remaining lifetime (e.g. root remaining 8760h -> intermediate_lifetime 720h), then restart Caddy.","Check the actual root expiry: caddy trust / inspect the root cert in storage (storage/caddy/pki/<id>/ca/root.crt) with openssl x509 -enddate; if it is nearly expired, import a fresh root or delete the CA assets so Caddy regenerates them.","If the root is external and short-lived, use the intermediate {} block to supply a pre-made intermediate with a suitable lifetime instead of letting Caddy generate one.","If you intended sign_with_root semantics, set sign_with_root so no generated intermediate is required."],"exampleFix":"// before (caddy.json, apps.pki.cAs[0])\n{\"id\":\"local\",\"intermediate_lifetime\":14400000000000}\n// after\n{\"id\":\"local\",\"intermediate_lifetime\":3600000000000}","handlingStrategy":"validation","validationCode":"// Go, before relying on a CA: derive the root's remaining lifetime and compare\nroot := getRootCert(t) // *x509.Certificate you distribute/import\nremaining := time.Until(root.NotAfter)\ndesired := 12 * time.Hour\nif desired >= remaining {\n    desired = remaining / 2 // always strictly below\n}\n_ = desired // use as intermediate_lifetime","typeGuard":"func intermediateLifetimeOK(rootRemaining, intermediate time.Duration) bool {\n    return intermediate > 0 && intermediate < rootRemaining\n}","tryCatchPattern":null,"preventionTips":["When importing external roots, compute intermediate_lifetime as a fraction (e.g. 1/2) of the root's remaining validity at install time.","Never leave intermediate_lifetime at a large fixed value while using short-lived roots.","Alert when the imported root enters its final intermediate-lifetime window so you rotate before startup fails."],"tags":["pki","certificates","validation","lifetime","config"],"backgroundTag":null,"analyzedSha":"50e54ee279aa1e504fe218ca49ab6ae16c100410","analyzedAt":"2026-08-15T09:20:21.641Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}