{"record":{"id":"61ca292a8ae534e8","repo":"hashicorp/terraform","slug":"failed-to-read-ssh-private-key-no-key-found","errorCode":null,"errorMessage":"Failed to read ssh private key: no key found","messagePattern":"Failed to read ssh private key: no key found","errorType":"console","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/communicator/ssh/provisioner.go","lineNumber":426,"sourceCode":"\tusigner, err := ssh.NewSignerFromKey(rawPk)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to create signer from raw private key %q: %s\", rawPk, err)\n\t}\n\n\tucertSigner, err := ssh.NewCertSigner(pcert.(*ssh.Certificate), usigner)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to create cert signer %q: %s\", usigner, err)\n\t}\n\n\treturn ssh.PublicKeys(ucertSigner), nil\n}\n\nfunc readPrivateKey(pk string) (ssh.AuthMethod, error) {\n\t// We parse the private key on our own first so that we can\n\t// show a nicer error if the private key has a password.\n\tblock, _ := pem.Decode([]byte(pk))\n\tif block == nil {\n\t\treturn nil, errors.New(\"Failed to read ssh private key: no key found\")\n\t}\n\tif block.Headers[\"Proc-Type\"] == \"4,ENCRYPTED\" {\n\t\treturn nil, errors.New(\n\t\t\t\"Failed to read ssh private key: password protected keys are\\n\" +\n\t\t\t\t\"not supported. Please decrypt the key prior to use.\")\n\t}\n\n\tsigner, err := ssh.ParsePrivateKey([]byte(pk))\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"Failed to parse ssh private key: %s\", err)\n\t}\n\n\treturn ssh.PublicKeys(signer), nil\n}\n\nfunc connectToAgent(connInfo *connectionInfo) (*sshAgent, error) {\n\tif !connInfo.Agent {\n\t\t// No agent configured","sourceCodeStart":408,"sourceCodeEnd":444,"githubUrl":"https://github.com/hashicorp/terraform/blob/c9def3e214014c1188faabfc4a5bde5095139765/internal/communicator/ssh/provisioner.go#L408-L444","documentation":"Thrown by readPrivateKey() in the SSH communicator when pem.Decode() returns a nil block for the configured private key string. This means the value supplied to the connection block's private_key is not a valid PEM-encoded block at all. Terraform cannot even begin to parse it as a key.","triggerScenarios":"Calling the SSH communicator (provisioner or connection block) where private_key is empty, a literal file PATH instead of file contents, a single-line string with stripped newlines, or arbitrary garbage.","commonSituations":"Using `private_key = var.key_path` (the path string) instead of `private_key = file(var.key_path)`; a CI/CD variable that collapsed the key to one line or left it empty; reading the key with a helper that trimmed whitespace; copy-paste that dropped the BEGIN/END markers.","solutions":["Wrap the path with file(): private_key = file(\"~/.ssh/id_rsa\") so the actual PEM bytes are loaded.","Confirm the variable is populated: output the first 10 chars and check it starts with '-----BEGIN'.","If sourcing from a variable, ensure no tool (terraform fmt, YAML, env injection) stripped the embedded newlines.","Verify you are not accidentally passing a public key or a known_hosts entry."],"exampleFix":"# before\nconnection {\n  private_key = var.ssh_key_path   # passes the PATH string, not the key\n}\n# after\nconnection {\n  private_key = file(var.ssh_key_path)   # loads PEM bytes from disk\n}","handlingStrategy":"validation","validationCode":"# validate the key is real PEM before the connection block consumes it\nvariable \"ssh_key_path\" { type = string }\nlocals {\n  key_bytes = file(var.ssh_key_path)\n  is_pem    = strcontains(local.key_bytes, \"-----BEGIN\")\n}\ncheck \"key_valid\" {\n  assert {\n    condition     = local.is_pem\n    error_message = \"ssh_key_path does not point to a PEM private key\"\n  }\n}\nconnection { private_key = local.key_bytes }","typeGuard":"# HCL guard: only treat the value as a key if it looks like PEM\nlocals {\n  safe_key = strcontains(coalesce(var.ssh_key, \"\"), \"-----BEGIN\") ? var.ssh_key : file(var.ssh_key_path)\n}","tryCatchPattern":"# use try() to fall back to file() if a raw value isn't usable\nconnection {\n  private_key = try(var.ssh_key, file(var.ssh_key_path))\n}","preventionTips":["Always load keys via file() from a path, never inline a raw value.","Lint connection blocks in CI so private_key is always file() or a known-good variable.","Keep keys in a secrets manager and inject them as complete PEM strings, preserving newlines."],"tags":["ssh","connection","pem","configuration"],"analyzedSha":"c9def3e214014c1188faabfc4a5bde5095139765","analyzedAt":"2026-08-07T15:39:49.278Z","schemaVersion":2},"datasetVersion":"2026-08-07T21:17:07.882Z"}