caddyserver/caddy · critical

decoding root key: %v

Error message

decoding root key: %v

What it means

The root key PEM was retrieved from storage but certmagic.PEMDecodePrivateKey could not decode it into a crypto.Signer. The bytes exist but are not a valid private-key PEM (wrong block type, unsupported algorithm, or corrupt base64), so the CA cannot use the root for signing.

Source

Thrown at modules/caddypki/ca.go:311

		if err != nil {
			return nil, nil, fmt.Errorf("generating root: %v", err)
		}
	}

	if rootCert == nil {
		rootCert, err = pemDecodeCertificate(rootCertPEM)
		if err != nil {
			return nil, nil, fmt.Errorf("parsing root certificate PEM: %v", err)
		}
	}
	if rootKey == nil {
		rootKeyPEM, err := ca.storage.Load(ca.ctx, ca.storageKeyRootKey())
		if err != nil {
			return nil, nil, fmt.Errorf("loading root key: %v", err)
		}
		rootKey, err = certmagic.PEMDecodePrivateKey(rootKeyPEM)
		if err != nil {
			return nil, nil, fmt.Errorf("decoding root key: %v", err)
		}
	}

	return rootCert, rootKey, nil
}

func (ca CA) genRoot() (rootCert *x509.Certificate, rootKey crypto.Signer, err error) {
	repl := ca.newReplacer()

	rootCert, rootKey, err = generateRoot(repl.ReplaceAll(ca.RootCommonName, ""))
	if err != nil {
		return nil, nil, fmt.Errorf("generating CA root: %v", err)
	}
	rootCertPEM, err := pemEncodeCert(rootCert.Raw)
	if err != nil {
		return nil, nil, fmt.Errorf("encoding root certificate: %v", err)
	}
	err = ca.storage.Store(ca.ctx, ca.storageKeyRootCert(), rootCertPEM)

View on GitHub (pinned to 50e54ee279)

Solutions

  1. Replace the stored key with a valid unencrypted PEM private key that matches the stored root.crt (openssl pkey -in key.pem -noout to validate; remove -aes256 passphrases when exporting).
  2. If the original key is gone, regenerate CA assets in storage and re-trust the new root.
  3. Ensure the key matches the cert: compare openssl x509 -pubkey vs openssl pkey -pubout outputs.
  4. Keep automated tooling from re-writing storage objects in place; write-and-rename instead.

Example fix

# before: storage key.pem is encrypted
-----BEGIN ENCRYPTED PRIVATE KEY-----...
# after: unencrypted, matching the root cert
openssl pkey -in enc.key -out key.pem
-----BEGIN PRIVATE KEY-----...
Defensive patterns

Strategy: validation

Validate before calling

// Reject encrypted/mismatched keys before startup
block, _ := pem.Decode(keyPEM)
if block == nil || strings.Contains(block.Type, "ENCRYPTED") { log.Fatal("key must be unencrypted PEM") }

Type guard

func isUnencryptedPrivateKeyPEM(b []byte) bool {
    block, _ := pem.Decode(b)
    return block != nil && (block.Type == "PRIVATE KEY" || block.Type == "EC PRIVATE KEY" || block.Type == "RSA PRIVATE KEY")
}

Try / catch

if strings.Contains(err.Error(), "decoding root key") {
    // replace stored key with unencrypted PEM matching root.crt, or regenerate CA
}

Prevention

When it happens

Trigger: key.pem contains garbage, a certificate instead of a key, an encrypted/passphrase-protected key (certmagic cannot decrypt it), or a PEM header/type PEMDecodePrivateKey rejects (e.g. non-PEM DER dumped raw). Raised in loadOrGenRoot right after loading the key bytes.

Common situations: User replaced storage key.pem with an OpenSSL-generated encrypted key ('-----BEGIN ENCRYPTED PRIVATE KEY-----'); scripts writing DER or PKCS#1/PKCS#8 variants that decode fails on; truncation from a disk-full event.

Related errors


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