hashicorp/terraform · error
failed to parse private key
Error message
failed to parse private key %q: %s
What it means
In signCertWithPrivateKey, the user-supplied private key string is parsed via ssh.ParseRawPrivateKey. If the key is not valid PEM, is corrupted, uses an unsupported algorithm, or is in the wrong format, parsing fails. SECURITY NOTE: the error message interpolates the full private key material via %q, potentially exposing secrets in logs and error output.
Solutions
- Verify the private_key value is a complete, unmodified PEM private key (begins with -----BEGIN ... PRIVATE KEY-----).
- Ensure you are not accidentally using the .pub file or a public key string.
- If using ed25519 or new-format keys, update golang.org/x/crypto to a version that supports them, or regenerate an RSA/ECDSA key.
- Check for newline corruption: the key must preserve all newlines. If loading from a file, use file() not a variable that may have been mangled.
- Strip any trailing whitespace or CRLF line endings from the key material.
Example fix
# before — accidentally using public key or mangled key
connection {
private_key = var.some_key # might be .pub or truncated
certificate = var.cert
}
# after — valid PEM private key with preserved newlines
connection {
private_key = file("~/.ssh/id_rsa") # not id_rsa.pub
certificate = file("~/.ssh/id_rsa-cert.pub")
} Defensive patterns
Strategy: validation
Validate before calling
// Pre-validate the private key parses before using it in a connection
import "golang.org/x/crypto/ssh"
func validatePrivateKey(pk string) error {
_, err := ssh.ParseRawPrivateKey([]byte(pk))
if err != nil {
// Do NOT log pk contents — it is secret
return fmt.Errorf("private key is not parseable: %w", err)
}
return nil
} Type guard
func isValidPEMPrivateKey(pk string) bool {
block, _ := pem.Decode([]byte(pk))
return block != nil &&
(strings.Contains(block.Type, "PRIVATE KEY")) &&
block.Headers["Proc-Type"] != "4,ENCRYPTED"
} Try / catch
// Wrap signCertWithPrivateKey; never log the key material
signer, err := signCertWithPrivateKey(privateKey, cert)
if err != nil {
// Log a generic message — DO NOT include pk or certificate in logs
return fmt.Errorf("SSH certificate authentication setup failed: %w", sanitizeErr(err))
} Prevention
- Never log or display private key material — the current error message interpolates pk via %q, which is a security risk.
- Validate keys with ssh-keygen -y -f keyfile before using them in Terraform.
- Use file() references for keys rather than pasting into variables to avoid newline corruption.
- Keep golang.org/x/crypto updated to support modern key algorithms.
When it happens
Trigger: Configuring an SSH connection with both a private_key and a certificate where the private_key value is not a parseable OpenSSH/PKCS PEM key. Triggered by: pasting a public key instead of a private key, providing an OpenSSH new-format key that ParseRawPrivateKey doesn't support in older x/crypto versions, truncated key, or wrong-line-ending corruption.
Common situations: User accidentally references the .pub file in private_key. Key was generated with a newer ssh-keygen format (ed25519 or RFC 4716) unsupported by the linked golang.org/x/crypto version. Key pasted from a secrets manager that stripped newlines. Key has Windows CRLF line endings that confuse the PEM decoder.
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 cert signer
- failed to create signer from raw private key
- failed to parse certificate
- Failed to parse ssh private key
- Failed to read ssh private key: no key found
AI-assisted analysis of hashicorp/terraform@d32a084675 (2026-08-11).
Data as JSON: /api/errors/58bd12f10113ee32.
Report an issue: GitHub.
Appendix: source
Thrown at internal/communicator/ssh/provisioner.go:400
if opts.password != "" {
conf.Auth = append(conf.Auth, ssh.Password(opts.password))
conf.Auth = append(conf.Auth, ssh.KeyboardInteractive(
PasswordKeyboardInteractive(opts.password)))
}
if opts.sshAgent != nil {
conf.Auth = append(conf.Auth, opts.sshAgent.Auth())
}
return conf, nil
}
// Create a Cert Signer and return ssh.AuthMethod
func signCertWithPrivateKey(pk string, certificate string) (ssh.AuthMethod, error) {
rawPk, err := ssh.ParseRawPrivateKey([]byte(pk))
if err != nil {
return nil, fmt.Errorf("failed to parse private key %q: %s", pk, err)
}
pcert, _, _, _, err := ssh.ParseAuthorizedKey([]byte(certificate))
if err != nil {
return nil, fmt.Errorf("failed to parse certificate %q: %s", certificate, err)
}
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), nilView on GitHub (pinned to d32a084675)