caddyserver/caddy · critical

decoding intermediate certificate PEM

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.

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.

Appendix: 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)