hashicorp/terraform · error
SSH authentication failed
Error message
SSH authentication failed (%s@%s): %w
What it means
Wraps the underlying x/crypto/ssh NewClientConn failure during the SSH handshake for remote-exec/file provisioners. Despite the name 'authentication failed', it covers any handshake-time failure reported by the SSH library: bad credentials, wrong key, host key mismatch, unsupported auth method, or the server rejecting the user. Terraform logs it as WARN and retries within the connection timeout because hosts sometimes boot the sshd before auth data is ready.
Solutions
- Verify the user/key combination by SSHing manually from the same machine: ssh -i <key> <user>@<host>.
- Confirm the correct username for the AMI/distribution (ubuntu for Ubuntu, ec2-user for Amazon Linux, admin for Debian, etc.).
- Check the private_key file format (OpenSSH, not PuTTY PPK) and that it is unencrypted or provided via an agent.
- Allow more startup time via connection.timeout, since sshd may precede auth readiness.
- If host key verification is involved, set host_key appropriately or disable strict checking deliberately.
Example fix
// before
connection {
user = "root"
private_key = file("~/.ssh/id_rsa")
host = aws_instance.web.public_ip
}
// after
connection {
user = "ubuntu"
private_key = file("~/.ssh/ubuntu-key")
host = aws_instance.web.public_ip
} Defensive patterns
Strategy: validation
Validate before calling
# Smoke-test SSH before apply to catch auth issues early:
# $ ssh -i ~/.ssh/<key> -o StrictHostKeyChecking=no <user>@<host> 'echo ok'
# In HCL, derive user/key from data sources rather than hardcoding:
# data "aws_key_pair" "kp" { ... } ; private_key = tls_private_key.kp.private_key_pem Try / catch
// In Go wrapping the provisioner, classify SSH auth failures as retryable
// within a short window (host may still be booting), then fatal:
if strings.Contains(err.Error(), "SSH authentication failed") {
if attempt < maxAttempts {
time.Sleep(backoff); continue
}
return fmt.Errorf("ssh auth failed after retries; check user/key: %w", err)
} Prevention
- Use the correct default user per AMI/distribution (ubuntu, ec2-user, admin).
- Generate keys with tls_private_key and reference the PEM directly.
- Smoke-test SSH manually before running apply on new images.
- Allow enough startup time via connection.timeout.
When it happens
Trigger: Wrong private_key or password for the target user; the user account does not exist on the remote; key is encrypted or in the wrong format; sshd is up but PAM/authorized_keys not yet populated (common on fresh cloud instances); algorithm/KEX mismatch with an old or restricted sshd.
Common situations: Using the default user on an AMI that requires a different one (e.g. 'ec2-user' vs 'ubuntu' vs 'admin'); pointing private_key at the wrong file; the instance's cloud-init has not finished injecting the public key; FIPS/hardened sshd that disabled the negotiated algorithms.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- Error creating new client connection via proxy
- Connection Error: StatusCode
- 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/3daba2b89362e265.
Report an issue: GitHub.
Appendix: source
Thrown at internal/communicator/ssh/communicator.go:211
log.Printf("[DEBUG] Connecting to %s for SSH", hostAndPort)
c.conn, err = c.config.connection()
if err != nil {
// Explicitly set this to the REAL nil. Connection() can return
// a nil implementation of net.Conn which will make the
// "if c.conn == nil" check fail above. Read here for more information
// on this psychotic language feature:
//
// http://golang.org/doc/faq#nil_error
c.conn = nil
log.Printf("[ERROR] connection error: %s", err)
return err
}
log.Printf("[DEBUG] Connection established. Handshaking for user %v", c.connInfo.User)
sshConn, sshChan, req, err := ssh.NewClientConn(c.conn, hostAndPort, c.config.config)
if err != nil {
err = fmt.Errorf("SSH authentication failed (%s@%s): %w", c.connInfo.User, hostAndPort, err)
// While in theory this should be a fatal error, some hosts may start
// the ssh service before it is properly configured, or before user
// authentication data is available.
// Log the error, and allow the provisioner to retry.
log.Printf("[WARN] %s", err)
return err
}
c.client = ssh.NewClient(sshConn, sshChan, req)
if c.config.sshAgent != nil {
log.Printf("[DEBUG] Telling SSH config to forward to agent")
if err := c.config.sshAgent.ForwardToAgent(c.client); err != nil {
return fatalError{err}
}
log.Printf("[DEBUG] Setting up a session to request agent forwarding")View on GitHub (pinned to d32a084675)