elastic/elasticsearch · error · SslConfigException

could not load ssl private key file [{}]

Error message

could not load ssl private key file [{}]

What it means

Thrown by PemKeyConfig.getPrivateKey() when PemUtils.parsePrivateKey() returns null for the configured PEM key path. A null return means the file was read but did not contain any recognised private-key block, so no key material could be extracted.

Source

Thrown at libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/PemKeyConfig.java:124

        }
        final Certificate leafCertificate = certificates.get(0);
        if (leafCertificate instanceof X509Certificate x509Certificate) {
            return List.of(Tuple.tuple(getPrivateKey(keyPath), x509Certificate));
        } else {
            return List.of();
        }
    }

    @Override
    public SslTrustConfig asTrustConfig() {
        return new PemTrustConfig(List.of(certificate), configBasePath);
    }

    private PrivateKey getPrivateKey(Path path) {
        try {
            final PrivateKey privateKey = PemUtils.parsePrivateKey(path, () -> keyPassword);
            if (privateKey == null) {
                throw new SslConfigException("could not load ssl private key file [" + path + "]");
            }
            return privateKey;
        } catch (SecurityException e) {
            throw SslFileUtil.accessControlFailure(KEY_FILE_TYPE, List.of(path), e, configBasePath);
        } catch (IOException e) {
            throw SslFileUtil.ioException(KEY_FILE_TYPE, List.of(path), e, null, configBasePath);
        } catch (GeneralSecurityException e) {
            throw SslFileUtil.securityException(KEY_FILE_TYPE, List.of(path), e);
        }
    }

    private List<Certificate> getCertificates(Path path) {
        try {
            return PemUtils.readCertificates(Collections.singleton(path));
        } catch (SecurityException e) {
            throw SslFileUtil.accessControlFailure(CERT_FILE_TYPE, List.of(path), e, configBasePath);
        } catch (IOException e) {
            throw SslFileUtil.ioException(CERT_FILE_TYPE, List.of(path), e, null, configBasePath);

View on GitHub (pinned to db6a809a66)

Solutions

  1. Confirm the file contains a private key: `head -1 key.pem` should show a known header (e.g. `-----BEGIN PRIVATE KEY-----`).
  2. Convert OpenSSH-format keys: `ssh-keygen -p -m PEM -f id_rsa` or `openssl pkey -in id_ed25519 -out ed25519.pem`.
  3. If only a public key was supplied by mistake, generate or locate the matching private key.
  4. Re-export in PKCS#8: `openssl pkcs8 -topk8 -inkey key.pem -out key.pk8.pem -nocrypt`.

Example fix

# before: OpenSSH private key fed to PemKeyConfig
# key: id_ed25519  (BEGIN OPENSSH PRIVATE KEY)

# after: convert to PKCS#8 PEM
ssh-keygen -e -m PKCS8 -f id_ed25519 > id_ed25519.pub
openssl pkey -in id_ed25519 -out id_ed25519.pem
# now configure key: id_ed25519.pem
Defensive patterns

Strategy: validation

Validate before calling

// Confirm the file is a recognised private-key PEM before configuring it.
public static boolean looksLikePrivateKeyPem(Path p) throws IOException {
    try (BufferedReader r = Files.newBufferedReader(p, StandardCharsets.UTF_8)) {
        String line;
        while ((line = r.readLine()) != null) {
            if (line.startsWith("-----BEGIN")) {
                return line.contains("PRIVATE KEY") || line.contains("DSA PARAMETERS") || line.contains("EC PARAMETERS");
            }
        }
    }
    return false;
}

Prevention

When it happens

Trigger: PemKeyConfig.getPrivateKey(Path) calls PemUtils.parsePrivateKey(path, passwordSupplier); the result is null. The parse loop scans for a '-----BEGIN' header; if none of the recognised formats (PKCS#1 RSA, PKCS#8, PKCS#8 encrypted, OpenSSL DSA, OpenSSL EC) is matched, parsePrivateKey throws a 'supported key format' error rather than returning null — so a null here typically indicates a parse failure inside a sub-parser that returned null unexpectedly, or a degenerate file.

Common situations: Empty key file, file containing only a public key, file containing only a certificate chain, or a file with an unrecognised PEM header (e.g. `-----BEGIN OPENSSH PRIVATE KEY-----`).

Understand the failure class

Related errors


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