caddyserver/caddy · critical

decoding intermediate key: %v

Error message

decoding intermediate key: %v

What it means

The intermediate key PEM bytes were loaded from storage, but certmagic.PEMDecodePrivateKey could not decode them into a signer. The stored key is invalid: wrong PEM block type, encrypted key, unsupported format, or corrupted base64. The intermediate chain may be perfectly fine, but signing is impossible without a usable key.

Source

Thrown at modules/caddypki/ca.go:376

		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)
	if err != nil {
		return nil, nil, fmt.Errorf("encoding intermediate certificate: %v", err)
	}
	err = ca.storage.Store(ca.ctx, ca.storageKeyIntermediateCert(), interCertPEM)

View on GitHub (pinned to 50e54ee279)

Solutions

  1. Restore a valid, unencrypted PEM private key matching intermediate.crt (validate: openssl pkey -in key.pem -noout; decrypt with openssl pkey first if needed).
  2. If no valid key remains, delete the intermediate cert+key objects and restart so Caddy regenerates the intermediate under the existing root.
  3. Confirm key/cert pairing by comparing public keys.
  4. Keep storage objects untouched by external tooling.

Example fix

# before: encrypted key in storage
-----BEGIN ENCRYPTED PRIVATE KEY-----...
# after
openssl pkey -in enc.key -out intermediate.key.pem
-----BEGIN PRIVATE KEY-----...
Defensive patterns

Strategy: validation

Validate before calling

// Reject undecodable keys before startup
block, _ := pem.Decode(interKeyPEM)
if block == nil || strings.Contains(block.Type, "ENCRYPTED") { fail("intermediate key must be unencrypted PEM") }

Type guard

func isUsablePrivateKeyPEM(b []byte) bool {
    blk, _ := pem.Decode(b)
    if blk == nil || strings.Contains(blk.Type, "ENCRYPTED") { return false }
    _, err := certmagicPEMDecodePrivateKeyMock(b) // or x509.ParsePKCS8PrivateKey etc.
    return err == nil
}

Try / catch

if strings.Contains(err.Error(), "decoding intermediate key") {
    // re-store an unencrypted PEM key matching intermediate.crt, or regenerate intermediate
}

Prevention

When it happens

Trigger: intermediate key object contains an encrypted ('BEGIN ENCRYPTED PRIVATE KEY') or otherwise undecodable PEM, a certificate instead of a key, raw DER, or truncated bytes. Raised in loadOrGenIntermediate after the cert chain loaded successfully.

Common situations: Operator replaced the key with an OpenSSL-exported passphrase-protected key; scripts writing DER; truncation after disk-full; copy/paste errors when restoring from backups.

Related errors


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