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

  1. Restore intermediate key.pem to the expected storage key so it pairs with intermediate.crt.
  2. 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.
  3. Repair permissions on the CA storage directory.
  4. 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

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


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