{"record":{"id":"d2debf1e3432ece8","repo":"slackhq/nebula","slug":"failed-to-parse-private-key-s","errorCode":null,"errorMessage":"failed to parse private key: %s","messagePattern":"failed to parse private key: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"sshd/server.go","lineNumber":107,"sourceCode":"\t\tPublicKeyCallback: cc.Authenticate,\n\t\tServerVersion:     fmt.Sprintf(\"SSH-2.0-Nebula???\"),\n\t}\n\n\ts.RegisterCommand(&Command{\n\t\tName:             \"help\",\n\t\tShortDescription: \"prints available commands or help <command> for specific usage info\",\n\t\tCallback: func(a any, args []string, w StringWriter) error {\n\t\t\treturn helpCallback(s.commands, args, w)\n\t\t},\n\t})\n\n\treturn s, nil\n}\n\nfunc (s *SSHServer) SetHostKey(hostPrivateKey []byte) error {\n\tprivate, err := ssh.ParsePrivateKey(hostPrivateKey)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to parse private key: %s\", err)\n\t}\n\n\ts.config.AddHostKey(private)\n\treturn nil\n}\n\nfunc (s *SSHServer) ClearTrustedCAs() {\n\ts.authLock.Lock()\n\ts.trustedCAs = []ssh.PublicKey{}\n\ts.authLock.Unlock()\n}\n\nfunc (s *SSHServer) ClearAuthorizedKeys() {\n\ts.authLock.Lock()\n\ts.trustedKeys = make(map[string]map[string]bool)\n\ts.authLock.Unlock()\n}\n","sourceCodeStart":89,"sourceCodeEnd":125,"githubUrl":"https://github.com/slackhq/nebula/blob/dd8f660c0ac37903ec4080ca4d3c861ba9342ceb/sshd/server.go#L89-L125","documentation":"SetHostKey parses the SSH host private key with golang.org/x/crypto/ssh.ParsePrivateKey and wraps any parse failure as 'failed to parse private key'. This key is what the nebula SSH debug server presents as its host key. Called from configSSH during config load, so a bad key aborts startup.","triggerScenarios":"configSSH passes the raw bytes of ssh.host_key (file contents) to SetHostKey and ParsePrivateKey fails: unsupported key format, encrypted (passphrase-protected) key, empty or truncated file, or the config pointed at a public key / non-key file.","commonSituations":"Generating an RSA key with new OpenSSH format not supported by an older library version; using a passphrase-protected key without decrypting it; pointing host_key at the .pub file; YAML path mistakes yielding empty bytes.","solutions":["Generate a supported key: ssh-keygen -t ed25519 -m PEM -N '' -f nebula_host_key (no passphrase)","Point the ssh.host_key config at the private key file, not the .pub file","Decrypt or regenerate the key without a passphrase (ParsePrivateKey does not prompt)","If the key is PKCS#1/OpenSSH-format incompatibility, convert with ssh-keygen -p -m PEM"],"exampleFix":"// before\nssh-keygen -t ed25519 -f host_key   # passphrase-protected, parse fails\n// after\nssh-keygen -t ed25519 -m PEM -N \"\" -f host_key","handlingStrategy":"validation","validationCode":"# Validate the host key parses before deploying nebula\nssh-keygen -y -f host_key > /dev/null && echo OK\n# Or in Go:\nif _, err := ssh.ParsePrivateKey(hostKeyBytes); err != nil {\n\tlog.Fatalf(\"host key invalid: %v\", err)\n}","typeGuard":null,"tryCatchPattern":"try {\n  loadConfig(path)\n} catch (e) {\n  if (e.message.includes(\"failed to parse private key\")) {\n    console.error(\"check ssh.host_key: must be an unencrypted PEM private key, not the .pub file\")\n  }\n  throw e\n}","preventionTips":["Generate keys with no passphrase: ssh-keygen -t ed25519 -m PEM -N \"\"","Point ssh.host_key at the private key file, never the .pub","Run a parse check (ssh-keygen -y) as part of config CI before rollout"],"tags":["ssh","config","private-key","parse-error","nebula"],"backgroundTag":"ssh-key-parse-failed","analyzedSha":"dd8f660c0ac37903ec4080ca4d3c861ba9342ceb","analyzedAt":"2026-09-03T11:13:55.444Z","contentChangedAt":"2026-09-03T11:13:55.444Z","schemaVersion":2},"datasetVersion":"2026-09-10T17:17:09.494Z"}