{"record":{"id":"aeb4a27d9a198914","repo":"neondatabase/neon","slug":"no-private-key-found-in","errorCode":null,"errorMessage":"no private key found in {}","messagePattern":"no private key found in (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"libs/http-utils/src/tls_certs.rs","lineNumber":37,"sourceCode":"    let mut reader = std::io::Cursor::new(&cert_data);\n\n    let cert_chain = rustls_pemfile::certs(&mut reader)\n        .collect::<Result<Vec<_>, _>>()\n        .context(format!(\"failed parsing certificate from file {filename:?}\"))?;\n\n    Ok(cert_chain)\n}\n\npub async fn load_private_key(filename: &Utf8Path) -> anyhow::Result<PrivateKeyDer<'static>> {\n    let key_data = tokio::fs::read(filename)\n        .await\n        .context(format!(\"failed reading private key file {filename:?}\"))?;\n    let mut reader = std::io::Cursor::new(&key_data);\n\n    let key = rustls_pemfile::private_key(&mut reader)\n        .context(format!(\"failed parsing private key from file {filename:?}\"))?;\n\n    key.ok_or(anyhow::anyhow!(\n        \"no private key found in {}\",\n        filename.as_str(),\n    ))\n}\n\npub async fn load_certified_key(\n    key_filename: &Utf8Path,\n    cert_filename: &Utf8Path,\n) -> anyhow::Result<CertifiedKey> {\n    let cert_chain = load_cert_chain(cert_filename).await?;\n    let key = load_private_key(key_filename).await?;\n\n    let key = rustls::crypto::ring::default_provider()\n        .key_provider\n        .load_private_key(key)?;\n\n    let certified_key = CertifiedKey::new(cert_chain, key);\n    certified_key.keys_match()?;","sourceCodeStart":19,"sourceCodeEnd":55,"githubUrl":"https://github.com/neondatabase/neon/blob/8f60b04da47ffefe0e52bda2440134b42874eb75/libs/http-utils/src/tls_certs.rs#L19-L55","documentation":"load_private_key read the key file and rustls_pemfile::private_key parsed it without an I/O or PEM-decoding error, but found no PEM section it recognizes as a private key. rustls-pemfile only accepts 'RSA PRIVATE KEY' (PKCS#1), 'PRIVATE KEY' (PKCS#8), 'EC PRIVATE KEY' (SEC1), and encrypted PKCS#8 sections; a file containing only certificates, an OpenSSH-format key, or a DER/binary blob yields None, which becomes this error naming the file path.","triggerScenarios":"Pointing the TLS key setting at the certificate file (paths swapped) so the file has only 'CERTIFICATE' sections; using an ssh-keygen ed25519 key ('-----BEGIN OPENSSH PRIVATE KEY-----') which rustls-pemfile cannot parse; an empty or truncated file after a failed secret mount; a raw DER key not wrapped in base64 PEM armor.","commonSituations":"Kubernetes/Docker secrets mounted empty or as a directory at startup; cert-manager or vault output where the key and cert variable names were swapped; ops staff generating TLS keys with ssh-keygen instead of openssl; encrypted keys whose PEM section the reader skips without a password provider.","solutions":["Verify the file actually contains a private key section: grep 'PRIVATE KEY' /path/to/key (expect '-----BEGIN ... PRIVATE KEY-----')","If key/cert paths were swapped, point the key setting at the real key file and the cert setting at the chain","Regenerate or convert the key into a supported format: 'openssl genpkey -algorithm RSA' / '-algorithm EC' (PKCS#8), or 'openssl pkcs8 -topk8 -nocrypt -in old.key -out key.pem'","Check the secret/volume actually delivered bytes: 'wc -c' and 'head -1' on the file","For encrypted keys ('ENCRYPTED PRIVATE KEY'), decrypt first with 'openssl pkey -in key.enc -out key.pem'"],"exampleFix":"# before: key was made with ssh-keygen (OpenSSH format, unsupported)\n#   -----BEGIN OPENSSH PRIVATE KEY-----\nssh-keygen -t ed25519 -f tls.key\n\n# after: generate a PKCS#8 key rustls-pemfile can load\nopenssl genpkey -algorithm RSA -out tls.key\n# or convert an existing PEM key to unencrypted PKCS#8\nopenssl pkcs8 -topk8 -nocrypt -in tls_old.key -out tls.key","handlingStrategy":"validation","validationCode":"use tokio::io::AsyncReadExt;\n\n/// Fails with an actionable message before server start if the file has no\n/// PEM section rustls-pemfile recognizes as a private key.\npub async fn ensure_private_key_pem(path: &camino::Utf8Path) -> anyhow::Result<()> {\n    let mut data = String::new();\n    tokio::fs::File::open(path)\n        .await?\n        .read_to_string(&mut data)\n        .await?;\n    let has_key = data\n        .lines()\n        .any(|l| l.starts_with(\"-----BEGIN\") && l.contains(\"PRIVATE KEY-----\"));\n    anyhow::ensure!(\n        has_key,\n        \"{path} has no `-----BEGIN ... PRIVATE KEY-----` section \\\n         (supported: PKCS#1 RSA, PKCS#8, SEC1 EC; OpenSSH keys are NOT supported)\"\n    );\n    Ok(())\n}","typeGuard":null,"tryCatchPattern":"match tls_certs::load_certified_key(&key_path, &cert_path).await {\n    Ok(k) => k,\n    Err(e) if format!(\"{e:#}\").contains(\"no private key found\") => {\n        // Refuse to start with a precise hint instead of crashing later on TLS accept\n        anyhow::bail!(\n            \"TLS key {key_path} unusable: {e:#}. \\\n             Check key/cert paths are not swapped and the key is PKCS#8/PKCS#1/SEC1 PEM, \\\n             not OpenSSH or DER.\"\n        )\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Run 'grep PRIVATE KEY <keyfile>' in deployment scripts before starting the service","Always generate TLS keys with openssl (genpkey/pkcs8), never ssh-keygen","Name secret files explicitly (tls.key / tls.crt) and assert their first line in CI for mounted secrets","Keep key and cert paths adjacent in config so they are swapped together or not at all"],"tags":["rust","tls","rustls","pem","configuration","startup"],"backgroundTag":"tls-private-key-load-failed","analyzedSha":"8f60b04da47ffefe0e52bda2440134b42874eb75","analyzedAt":"2026-08-16T23:39:28.135Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}