caddyserver/caddy · critical
loading intermediate key: %v
Error message
loading intermediate key: %v
What it means
After loading an intermediate cert chain from storage, Caddy loads the intermediate private key (storageKeyIntermediateKey). Any storage error is wrapped as 'loading intermediate key'. Without the intermediate key the CA cannot sign leaf certificates, so setup fails even though the chain parsed fine.
Source
Thrown at modules/caddypki/ca.go:372
interCert, interKey, err = ca.genIntermediate(rootCert, rootKey)
if err != nil {
return nil, nil, fmt.Errorf("generating new intermediate cert: %v", err)
}
interCertChain = append(interCertChain, interCert)
}
if len(interCertChain) == 0 {
interCertChain, err = pemDecodeCertificateChain(interCertPEM)
if err != nil {
return nil, nil, fmt.Errorf("decoding intermediate certificate PEM: %v", err)
}
}
if interKey == nil {
interKeyPEM, err := ca.storage.Load(ca.ctx, ca.storageKeyIntermediateKey())
if err != nil {
return nil, nil, fmt.Errorf("loading intermediate key: %v", err)
}
interKey, err = certmagic.PEMDecodePrivateKey(interKeyPEM)
if err != nil {
return nil, nil, fmt.Errorf("decoding intermediate key: %v", err)
}
}
return interCertChain, interKey, nil
}
func (ca CA) genIntermediate(rootCert *x509.Certificate, rootKey crypto.Signer) (interCert *x509.Certificate, interKey crypto.Signer, err error) {
repl := ca.newReplacer()
interCert, interKey, err = generateIntermediate(repl.ReplaceAll(ca.IntermediateCommonName, ""), rootCert, rootKey, time.Duration(ca.IntermediateLifetime))
if err != nil {
return nil, nil, fmt.Errorf("generating CA intermediate: %v", err)
}
interCertPEM, err := pemEncodeCert(interCert.Raw)View on GitHub (pinned to 50e54ee279)
Solutions
- Restore intermediate key.pem to the expected storage key so it pairs with intermediate.crt.
- If lost, delete both intermediate cert and key objects (keep root) and restart to regenerate the intermediate; leafs signed by the old intermediate stay valid until expiry, new leafs come from the new one.
- Repair permissions on the CA storage directory.
- Ensure backups and multi-node storage copies always include every CA asset.
Defensive patterns
Strategy: try-catch
Validate before calling
// Pre-start: cert without key = partial state
_, c := os.Stat(caDir + "/intermediate.crt")
_, k := os.Stat(caDir + "/intermediate.key.pem")
if c == nil && k != nil { fail("intermediate key missing") } Type guard
func intermediateAssetsComplete(dir string) bool {
_, c := os.Stat(filepath.Join(dir, "intermediate.crt"))
_, k := os.Stat(filepath.Join(dir, "intermediate.key.pem"))
return (c == nil) == (k == nil)
} Try / catch
if strings.Contains(err.Error(), "loading intermediate key") {
// restore key, or drop intermediate cert+key objects to force regeneration under root
} Prevention
- Back up intermediate cert and key as a pair.
- Never delete one half of a CA asset pair during cleanup.
- Run asset-completeness checks after every restore or node sync.
When it happens
Trigger: storage.Load for the intermediate key errors: object deleted while intermediate.crt remains (partial state), permission/ownership problem on key.pem, custom storage backend failure. Runs on the load-existing path (interKey == nil after no generation happened).
Common situations: Key file removed separately from the cert (security cleanup gone wrong); backups that skipped key files; inconsistent replicated storage across nodes; storage prefix moved without copying all objects.
Related errors
- loading root key: %v
- saving root key: %v
- failed to get root and intermediate cert for CA %s: %v
- failed to provision CA %s, %w
- loading storage module: %v
AI-assisted analysis of caddyserver/caddy@50e54ee279 (2026-08-15).
Data as JSON: /api/errors/4a850381afa108c6.
Report an issue: GitHub.