caddyserver/caddy · error

encrypted private keys are not supported; please decrypt the

Error message

encrypted private keys are not supported; please decrypt the key first

What it means

The TLS file certificate loader (tls.certificates.load_files / FileLoader) reads the PEM key file and inspects its first 40 bytes; if they contain the string ENCRYPTED — the marker header of PKCS#8/PKCS#1 encrypted key PEMs such as '-----BEGIN ENCRYPTED PRIVATE KEY-----' — it refuses to load, because Go's tls.X509KeyPair cannot consume passphrase-protected keys and Caddy has no prompt/decrypt step. The error tells you to decrypt out-of-band.

Source

Thrown at modules/caddytls/fileloader.go:101

		certData, err := os.ReadFile(pair.Certificate)
		if err != nil {
			return nil, err
		}
		keyData, err := os.ReadFile(pair.Key)
		if err != nil {
			return nil, err
		}

		var cert tls.Certificate
		switch pair.Format {
		case "":
			fallthrough

		case "pem":
			// if the start of the key file looks like an encrypted private key,
			// reject it with a helpful error message
			if strings.Contains(string(keyData[:40]), "ENCRYPTED") {
				return nil, fmt.Errorf("encrypted private keys are not supported; please decrypt the key first")
			}

			cert, err = tls.X509KeyPair(certData, keyData)

		default:
			return nil, fmt.Errorf("unrecognized certificate/key encoding format: %s", pair.Format)
		}
		if err != nil {
			return nil, err
		}

		certs = append(certs, Certificate{Certificate: cert, Tags: pair.Tags})
	}
	return certs, nil
}

// Interface guard
var (

View on GitHub (pinned to 50e54ee279)

Solutions

  1. Decrypt the key to an unencrypted PEM: openssl rsa -in encrypted.key -out decrypted.key (RSA) or openssl pkey -in encrypted.key -out decrypted.key, then point the config at decrypted.key.
  2. Restrict the decrypted file's permissions to the Caddy user (chmod 600) since it is now plaintext.
  3. Prefer letting Caddy automate certificates (ACME) instead of supplying encrypted files.
  4. For automated pipelines, decrypt in a pre-deploy step fed from a secrets manager, never committing the plaintext key.

Example fix

# before
$ openssl genrsa -aes256 -out site.key 2048   # encrypted
tls /path/cert.pem /path/site.key  -> error

# after
$ openssl pkey -in site.key -out site.plain.key   # enter passphrase once
tls /path/cert.pem /path/site.plain.key
Defensive patterns

Strategy: validation

Validate before calling

// Detect encrypted PEM keys before handing them to the loader.
func isEncryptedKey(keyPath string) (bool, error) {
    data, err := os.ReadFile(keyPath)
    if err != nil {
        return false, err
    }
    head := data
    if len(head) > 40 {
        head = head[:40]
    }
    return strings.Contains(string(head), "ENCRYPTED"), nil
}

Try / catch

if err != nil && strings.Contains(err.Error(), "encrypted private keys are not supported") {
    // decrypt out-of-band: openssl pkey -in enc.key -out plain.key; update config path
}

Prevention

When it happens

Trigger: tls directive with load / pointing at a certificate/key pair whose key file begins with '-----BEGIN ENCRYPTED PRIVATE KEY-----' (openssl enc-protected RSA/EC keys).

Common situations: Keys generated with 'openssl genrsa -aes256' or exported from vaults/browsers with a passphrase; CI copying secured keys into place without decrypting; operators assuming Caddy will prompt for the passphrase (it won't, even interactively).

Related errors


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