elastic/elasticsearch · error · SslConfigException
cannot read PEM private key [{}] because the file does not c
Error message
cannot read PEM private key [{}] because the file does not contain a supported key format What it means
This SslConfigException is thrown by parsePrivateKey after the parser scans the file for a '-----BEGIN' header but no recognized PEM private-key header is found (the supported headers are PKCS#8, PKCS#8 ENCRYPTED, PKCS#1 RSA, OpenSSL DSA, DSA PARAMETERS, OpenSSL EC, and EC PARAMETERS). It is the catch-all for any file that is a PEM file but not one of the supported private-key formats. The absolute path is interpolated into the message so the offending file is easy to identify.
Source
Thrown at libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/PemUtils.java:157
char[] password = passwordSupplier.get();
if (password == null) {
throw new SslConfigException("cannot read encrypted key [" + keyPath.toAbsolutePath() + "] without a password");
}
return parsePKCS8Encrypted(bReader, password);
} else if (PKCS8_HEADER.equals(line.trim())) {
return parsePKCS8(bReader);
} else if (PKCS1_HEADER.equals(line.trim())) {
return parsePKCS1Rsa(bReader, passwordSupplier);
} else if (OPENSSL_DSA_HEADER.equals(line.trim())) {
return parseOpenSslDsa(bReader, passwordSupplier);
} else if (OPENSSL_DSA_PARAMS_HEADER.equals(line.trim())) {
return parseOpenSslDsa(removeDsaHeaders(bReader), passwordSupplier);
} else if (OPENSSL_EC_HEADER.equals(line.trim())) {
return parseOpenSslEC(bReader, passwordSupplier);
} else if (OPENSSL_EC_PARAMS_HEADER.equals(line.trim())) {
return parseOpenSslEC(removeECHeaders(bReader), passwordSupplier);
} else {
throw new SslConfigException(
"cannot read PEM private key ["
+ keyPath.toAbsolutePath()
+ "] because the file does not contain a supported key format"
);
}
}
}
/**
* Removes the EC Headers that OpenSSL adds to EC private keys as the information in them
* is redundant
*
* @throws IOException if the EC Parameter footer is missing
*/
private static BufferedReader removeECHeaders(BufferedReader bReader) throws IOException {
String line = bReader.readLine();
while (line != null) {
if (OPENSSL_EC_PARAMS_FOOTER.equals(line.trim())) {View on GitHub (pinned to db6a809a66)
Solutions
- Verify the file actually contains a private key: run 'head -1 <file>' and confirm the line is one of the supported BEGIN markers (PRIVATE KEY, RSA PRIVATE KEY, DSA PRIVATE KEY, EC PRIVATE KEY, ENCRYPTED PRIVATE KEY).
- If you pointed at a certificate by mistake, change the config to reference the private-key file (e.g. xpack.http.ssl.key instead of certificate).
- If the key is OpenSSH/ED25519 format ('-----BEGIN OPENSSH PRIVATE KEY-----'), regenerate it in PKCS#8 or PKCS#1 with OpenSSL: 'openssl pkcs8 -topk8 -in sshkey -out pkcs8key.pem' or 'openssl rsa -in sshkey -out rsakey.pem'.
- Regenerate the key with 'openssl genrsa -out key.pem 2048' (RSA) or 'openssl ecparam -genkey -name prime256v1 -out key.pem' (EC) to get a directly supported format.
Example fix
// before: config points at a certificate xpack.security.transport.ssl.key: /etc/elasticsearch/certs/node.crt.pem // after: config points at the PKCS#8 private key xpack.security.transport.ssl.key: /etc/elasticsearch/certs/node.key.pem
Defensive patterns
Strategy: validation
Validate before calling
// Before calling readPrivateKey, confirm the file's first BEGIN line is a supported private-key header
private static final Set<String> SUPPORTED_KEY_HEADERS = Set.of(
"-----BEGIN PRIVATE KEY-----",
"-----BEGIN ENCRYPTED PRIVATE KEY-----",
"-----BEGIN RSA PRIVATE KEY-----",
"-----BEGIN DSA PRIVATE KEY-----",
"-----BEGIN DSA PARAMETERS-----",
"-----BEGIN EC PRIVATE KEY-----",
"-----BEGIN EC PARAMETERS-----"
);
static void assertSupportedKey(Path keyPath) throws IOException {
try (BufferedReader r = Files.newBufferedReader(keyPath, StandardCharsets.UTF_8)) {
String line = r.readLine();
while (line != null && !line.startsWith("-----BEGIN")) line = r.readLine();
if (line == null || !SUPPORTED_KEY_HEADERS.contains(line.trim())) {
throw new IllegalArgumentException("File [" + keyPath + "] is not a supported PEM private key (first header: " + line + ")");
}
}
} Try / catch
try {
PrivateKey key = PemUtils.readPrivateKey(keyPath, passwordSupplier);
} catch (SslConfigException e) {
if (e.getMessage().contains("does not contain a supported key format")) {
// log + prompt user to supply a PKCS#8/PKCS#1/EC/DSA key
} else throw e;
} Prevention
- Adopt a deployment convention: name private-key files '*.key.pem' and certificate files '*.crt.pem' to avoid swapping them.
- In CI, run 'openssl pkey -in <keyfile> -noout' as a smoke test before deploying.
- Document the supported key formats (PKCS#8, PKCS#1 RSA, OpenSSL DSA/EC) next to the SSL config in your runbook.
When it happens
Trigger: Calling PemUtils.readPrivateKey(path, passwordSupplier) (or the package-private parsePrivateKey) where the first '-----BEGIN' line in the file is something like '-----BEGIN CERTIFICATE-----', '-----BEGIN PUBLIC KEY-----', '-----BEGIN X509 CRL-----', or any non-private-key PEM header. Also fires if the file has only a header that is not in the supported list (e.g. an OpenSSH-format key '-----BEGIN OPENSSH PRIVATE KEY-----').
Common situations: Pointing the SSL key configuration at a certificate file instead of the private key file; passing a public key PEM; using an OpenSSH/new-style ED25519 key generated by 'ssh-keygen -o' or 'ssh-keygen -t ed25519'; truncating/corrupting the key file so the first BEGIN line is unrecognised; mixing up the order of cert and key arguments in Elasticsearch xpack.ssl.* settings.
Related errors
- could not load ssl private key file [{}]
- Error parsing Private Key [{}], file is empty
- cannot read encrypted key [{}] without a password
- Expected ASN.1 object of type 0x{} but was 0x{}
- Invalid DER: stream too short, missing tag
AI-assisted analysis of elastic/elasticsearch@db6a809a66 (2026-08-12).
Data as JSON: /api/errors/6b9ef794688d7346.
Report an issue: GitHub.