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 PemUtils.readPrivateKey() (the public wrapper around parsePrivateKey) when parsePrivateKey returns null. readPrivateKey is the entry point used by other Elastic SSL code that wants SslFileUtil-style error wrapping; the null return means the file was readable but contained no recognised private-key PEM block.

Source

Thrown at libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/PemUtils.java:109

     */
    private static final String DEPRECATED_DES_EDE_ALGORITHM = "DESede";

    private PemUtils() {
        throw new IllegalStateException("Utility class should not be instantiated");
    }

    /**
     * Creates a {@link PrivateKey} from the contents of a file and handles any exceptions
     *
     * @param path           the path for the key file
     * @param passwordSupplier A password supplier for the potentially encrypted (password protected) key
     * @return a private key from the contents of the file
     */
    public static PrivateKey readPrivateKey(Path path, Supplier<char[]> passwordSupplier) throws IOException, GeneralSecurityException {
        try {
            final PrivateKey privateKey = PemUtils.parsePrivateKey(path, passwordSupplier);
            if (privateKey == null) {
                throw new SslConfigException("could not load ssl private key file [" + path + "]");
            }
            return privateKey;
        } catch (SecurityException e) {
            throw SslFileUtil.accessControlFailure("PEM private key", List.of(path), e, null);
        } catch (IOException e) {
            throw SslFileUtil.ioException("PEM private key", List.of(path), e);
        } catch (GeneralSecurityException e) {
            throw SslFileUtil.securityException("PEM private key", List.of(path), e);
        }
    }

    /**
     * Creates a {@link PrivateKey} from the contents of a file. Supports PKCS#1, PKCS#8
     * encoded formats of encrypted and plaintext RSA, DSA and EC(secp256r1) keys
     *
     * @param keyPath           the path for the key file
     * @param passwordSupplier A password supplier for the potentially encrypted (password protected) key
     * @return a private key from the contents of the file

View on GitHub (pinned to db6a809a66)

Solutions

  1. Inspect the first non-blank line of the file — it must start with `-----BEGIN` and match a supported header.
  2. Convert OpenSSH keys to PEM: `ssh-keygen -p -m PEM -f id_rsa`.
  3. Re-export as PKCS#8: `openssl pkcs8 -topk8 -inkey key.pem -out key.pk8.pem -nocrypt`.
  4. Verify the file is a private key (not a cert): `openssl pkey -in key.pem -noout` should succeed.

Example fix

// before: passing a public-key-only file
PrivateKey pk = PemUtils.readPrivateKey(pubKeyPath, () -> null); // throws

// after: pass the actual private key
PrivateKey pk = PemUtils.readPrivateKey(privateKeyPath, () -> keyPassword);
Defensive patterns

Strategy: validation

Validate before calling

public static boolean fileContainsPrivateKey(Path p) throws IOException {
    try (BufferedReader r = Files.newBufferedReader(p, StandardCharsets.UTF_8)) {
        String line;
        while ((line = r.readLine()) != null) {
            if (line.startsWith("-----BEGIN") && line.contains("PRIVATE KEY")) return true;
        }
    }
    return false;
}

Prevention

When it happens

Trigger: readPrivateKey(path, passwordSupplier) calls parsePrivateKey(path, passwordSupplier); result is null. This is the same condition as error 795 but surfaced through the PemUtils API rather than PemKeyConfig — it indicates the file lacks any of the supported headers (PKCS#1 RSA, PKCS#8, PKCS#8 encrypted, OpenSSL DSA, OpenSSL EC).

Common situations: File is empty, contains only a public key, contains an OpenSSH-format key, or is a certificate rather than a private key. Note that parsePrivateKey actually throws SslConfigException for unsupported formats rather than returning null, so a true null is rare and typically comes from a future code path that may return null for an empty/unparseable file.

Understand the failure class

Related errors


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