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
- 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).
- If the original key is gone, regenerate CA assets in storage and re-trust the new root.
- Ensure the key matches the cert: compare openssl x509 -pubkey vs openssl pkey -pubout outputs.
- 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 exporting keys with OpenSSL, omit passphrase flags (-aes*) for keys Caddy must read.
- Validate key PEMs (openssl pkey -noout) as part of deployment checks.
- Document that CA storage keys must remain in certmagic-compatible, unencrypted PEM.
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
- encoding root key: %v
- decoding intermediate key: %v
- loading signing key: %v
- generating root: %v
- parsing root certificate PEM: %v
AI-assisted analysis of caddyserver/caddy@50e54ee279 (2026-08-15).
Data as JSON: /api/errors/a3bd4e8a1a15bc0c.
Report an issue: GitHub.