hashicorp/terraform · error
Failed to read ssh private key: no key found
Error message
Failed to read ssh private key: no key found
What it means
Returned by `readPrivateKey` when `pem.Decode` of the configured SSH private key material returns a nil block — the supplied key string is not valid PEM (no `-----BEGIN ... -----` block at all). This is checked before password/encryption handling so the user gets a clear 'no key found' message rather than a cryptic parse error.
Solutions
- Verify the value is an unencrypted PEM private key (starts with `-----BEGIN OPENSSH PRIVATE KEY-----` or RSA/EC PRIVATE KEY).
- Check the `private_key`/`private_key_path` interpolation points at the private, not public, key.
- Ensure the file read or variable actually contains the key (no trailing newline issues, not empty).
Example fix
# before
connection { type = "ssh" private_key = file("~/.ssh/id_rsa.pub") } # public key -> error
# after
connection { type = "ssh" private_key = file("~/.ssh/id_rsa") } Defensive patterns
Strategy: validation
Validate before calling
// Validate the key parses as PEM before handing it to the connection:
if block, _ := pem.Decode([]byte(key)); block == nil {
return errors.New("private_key is not valid PEM")
} Type guard
// isValidPEMPrivateKey reports whether s decodes to a PEM block.
func isValidPEMPrivateKey(s string) bool {
block, _ := pem.Decode([]byte(s))
return block != nil
} Try / catch
if _, err := readPrivateKey(privateKey); err != nil {
return fmt.Errorf("invalid ssh private_key for connection: %w", err)
} Prevention
- Validate private_key content in a `check`/precondition before apply.
- Reference private keys, not public keys, in `connection.private_key`.
- Store keys in a secrets backend and interpolate; avoid hand-pasting that drops PEM headers.
When it happens
Trigger: `connection.private_key` (or the configured key path's contents) cannot be PEM-decoded: empty string, wrong variable interpolated, a public key pasted instead of a private key, or a file read error produced empty content.
Common situations: Passing `file("id_rsa.pub")` instead of the private key; a `${var.private_key}` that resolves to empty; copy-paste that dropped the BEGIN/END lines; trailing whitespace/formatting mangled the PEM.
Related errors
- Failed to read ssh private key: password protected keys…
- host for provisioner cannot be empty
- connection type ' ' not supported
- Error connecting to bastion
- Error connecting to proxy
AI-assisted analysis of hashicorp/terraform@d32a084675 (2026-08-11).
Data as JSON: /api/errors/61ca292a8ae534e8.
Report an issue: GitHub.
Appendix: source
Thrown at internal/communicator/ssh/provisioner.go:426
usigner, err := ssh.NewSignerFromKey(rawPk)
if err != nil {
return nil, fmt.Errorf("failed to create signer from raw private key %q: %s", rawPk, err)
}
ucertSigner, err := ssh.NewCertSigner(pcert.(*ssh.Certificate), usigner)
if err != nil {
return nil, fmt.Errorf("failed to create cert signer %q: %s", usigner, err)
}
return ssh.PublicKeys(ucertSigner), nil
}
func readPrivateKey(pk string) (ssh.AuthMethod, error) {
// We parse the private key on our own first so that we can
// show a nicer error if the private key has a password.
block, _ := pem.Decode([]byte(pk))
if block == nil {
return nil, errors.New("Failed to read ssh private key: no key found")
}
if block.Headers["Proc-Type"] == "4,ENCRYPTED" {
return nil, errors.New(
"Failed to read ssh private key: password protected keys are\n" +
"not supported. Please decrypt the key prior to use.")
}
signer, err := ssh.ParsePrivateKey([]byte(pk))
if err != nil {
return nil, fmt.Errorf("Failed to parse ssh private key: %s", err)
}
return ssh.PublicKeys(signer), nil
}
func connectToAgent(connInfo *connectionInfo) (*sshAgent, error) {
if !connInfo.Agent {
// No agent configuredView on GitHub (pinned to d32a084675)