hashicorp/terraform · error
Failed to parse ssh private key
Error message
Failed to parse ssh private key: %s
What it means
In readPrivateKey, the private key is first PEM-decoded manually. If PEM decoding succeeds (block is non-nil) and the key is not password-protected (no Proc-Type 4,ENCRYPTED header), ssh.ParsePrivateKey is called. This error fires when ParsePrivateKey still fails despite valid PEM — typically due to an unsupported algorithm, corrupted key body, or a key format the linked x/crypto version cannot parse. Note this path does NOT catch password-protected keys of newer formats (OpenSSH new format) where the Proc-Type header is absent.
Solutions
- Regenerate the SSH key with ssh-keygen using a standard format: ssh-keygen -t rsa -b 2048 -m PEM -f keyfile.
- If the key is in OpenSSH new format, convert to PEM: ssh-keygen -p -m PEM -f keyfile.
- Update golang.org/x/crypto to a version that supports your key's algorithm and format.
- Verify the key body is intact — compare against the original file with diff or a checksum.
- Ensure no trailing characters, BOM, or encoding issues are present in the key material.
Example fix
# before — key in a format ParsePrivateKey can't handle
connection {
private_key = var.pkcs8_or_new_format_key
}
# after — convert to standard PEM format
ssh-keygen -p -m PEM -f ~/.ssh/id_rsa
connection {
private_key = file("~/.ssh/id_rsa")
} Defensive patterns
Strategy: validation
Validate before calling
// Pre-validate the private key using the same logic as readPrivateKey
func validateReadPrivateKey(pk string) error {
block, _ := pem.Decode([]byte(pk))
if block == nil {
return errors.New("no PEM block found in private key")
}
if block.Headers["Proc-Type"] == "4,ENCRYPTED" {
return errors.New("password-protected keys are not supported; decrypt the key first")
}
_, err := ssh.ParsePrivateKey([]byte(pk))
return err
} Type guard
func isUnencryptedPEMKey(pk string) bool {
block, _ := pem.Decode([]byte(pk))
if block == nil {
return false
}
return block.Headers["Proc-Type"] != "4,ENCRYPTED"
} Try / catch
signer, err := ssh.ParsePrivateKey([]byte(pk))
if err != nil {
return nil, fmt.Errorf("cannot parse SSH private key (check format/algorithm): %w", err)
} Prevention
- Generate keys in PEM format: ssh-keygen -t rsa -m PEM -f keyfile.
- Decrypt password-protected keys before use: ssh-keygen -p -f keyfile.
- Note: the Proc-Type header check only catches old-format encrypted keys; OpenSSH new-format encrypted keys bypass this check and fail at ParsePrivateKey.
- Avoid passing TLS or other non-SSH PEM keys to the SSH connection.
When it happens
Trigger: Providing a private_key that is valid PEM (decodes successfully) but whose algorithm or internal structure ssh.ParsePrivateKey cannot handle. Triggered by: PKCS#8-wrapped keys unsupported by older x/crypto, keys with unexpected headers, or subtly corrupted key bodies that decode as PEM but fail cryptographic parsing.
Common situations: User provides a key generated by a newer ssh-keygen or by OpenSSL in PKCS#8 format that the x/crypto version doesn't support. Key was re-encoded or transformed by a secrets manager. Key uses a cipher or KDF that ParsePrivateKey doesn't recognize. Key is a valid PEM but for a non-SSH purpose (e.g., TLS certificate key) that happens to decode.
Understand the failure class
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- failed to create signer from raw private key
- failed to parse private key
- Failed to read ssh private key: no key found
- failed to create cert signer
- failed to parse certificate
AI-assisted analysis of hashicorp/terraform@d32a084675 (2026-08-11).
Data as JSON: /api/errors/6eca17b61b67e272.
Report an issue: GitHub.
Appendix: source
Thrown at internal/communicator/ssh/provisioner.go:436
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 configured
return nil, nil
}
agent, conn, err := sshagent.New()
if err != nil {
return nil, err
}
// connection close is handled over in Communicator
return &sshAgent{View on GitHub (pinned to d32a084675)