caddyserver/caddy · critical

decoding intermediate certificate PEM: %v

Error message

decoding intermediate certificate PEM: %v

What it means

An intermediate certificate PEM was loaded from storage, but pemDecodeCertificateChain could not parse it into a chain of x509 certificates. The stored intermediate.crt is corrupt, truncated, or contains a non-certificate PEM block. Because the intermediate is what signs leafs, this stops all issuance even though the root is fine.

Source

Thrown at modules/caddypki/ca.go:365

	interCertPEM, err := ca.storage.Load(ca.ctx, ca.storageKeyIntermediateCert())
	if err != nil {
		if !errors.Is(err, fs.ErrNotExist) {
			return nil, nil, fmt.Errorf("loading intermediate cert: %v", err)
		}

		// TODO: should we require that all or none of the assets are required before overwriting anything?
		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) {

View on GitHub (pinned to 50e54ee279)

Solutions

  1. Verify and, if corrupt, delete only the intermediate cert+key objects in storage (keep root) and restart — Caddy will regenerate a fresh intermediate signed by the existing root, and existing leafs remain valid until expiry.
  2. Restore a known-good intermediate.crt/key.pem pair from backup instead, if you need continuity of the intermediate.
  3. Fix disk space / storage atomicity so Store() cannot half-write again.
  4. Stop hand-editing storage objects; use Caddy's API/CLI for exports.
Defensive patterns

Strategy: validation

Validate before calling

// Pre-start: parse stored intermediate chain
if b, err := os.ReadFile(caDir + "/intermediate.crt"); err == nil {
    if _, err := parseCertChainPEM(b); err != nil { fail("intermediate cert corrupt") }
}

Type guard

func isParseableCertChainPEM(b []byte) bool {
    rest := b
    for {
        var block *pem.Block
        block, rest = pem.Decode(rest)
        if block == nil { break }
        if block.Type != "CERTIFICATE" { return false }
        if _, err := x509.ParseCertificate(block.Bytes); err != nil { return false }
    }
    return len(rest) == 0
}

Try / catch

if strings.Contains(err.Error(), "decoding intermediate certificate PEM") {
    // delete intermediate cert+key only (keep root) -> Caddy regenerates under same root
}

Prevention

When it happens

Trigger: storage.Load succeeds but the bytes fail PEM/x509 chain parsing: file truncated by a crash mid-write, a key PEM stored in intermediate.crt, base64 corruption, CRLF/BOM injected by editors or replication. Any startup/load of an existing CA with a stored intermediate.

Common situations: Partial writes from disk-full events (see the file's all-or-none TODO); operators editing or moving storage files by hand; storage replication mangling objects; backups restored incompletely.

Understand the failure class

Related errors


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