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

  1. 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).
  2. 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).
  3. 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'.
  4. 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

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


AI-assisted analysis of elastic/elasticsearch@db6a809a66 (2026-08-12). Data as JSON: /api/errors/6b9ef794688d7346. Report an issue: GitHub.