How to fix Error: ssh: handshake failed in Terraform

TerraformINTERMEDIATEMEDIUMUpdated Dec 30, 2025· 197 views

The SSH handshake failed error occurs when Terraform's remote-exec or file provisioner cannot establish a successful SSH connection to a remote host. This is typically due to authentication configuration issues, incorrect credentials, key type mismatches, or timing problems during instance initialization.

Quick fix

Fixes Error: ssh: handshake failed

ssh -i /path/to/private/key.pem ubuntu@instance-ip-address
  1. Verify SSH works manually first
  2. Use the correct private_key argument in connection block
  3. Match the SSH user to your AMI
  4. Ensure private key file permissions are correct
terraform — error
ssh -i /path/to/private/key.pem ubuntu@instance-ip-address
Error: ssh: handshake failed

What this error means

The "ssh: handshake failed" error indicates that while Terraform was able to initiate a TCP connection to the remote host on port 22, the SSH protocol handshake and authentication phase failed. This error commonly appears in several contexts: - **Terraform remote-exec provisioner**: When running commands on newly created instances via SSH - **Terraform file provisioner**: When uploading or downloading files via SCP (which uses SSH) - **SSH connection block**: When configuring how Terraform should communicate with a resource The handshake failure typically has one of two root causes: 1. **Authentication failed**: The provided credentials (key or password) don't match what the remote host expects 2. **Connection configuration mismatch**: The SSH client and server negotiated incompatible settings during the handshake phase The error message may include additional details like "ssh: unable to authenticate, attempted methods [none publickey], no supported methods remain" which tells you what authentication methods were attempted.

Typical symptoms

  • Terraform apply fails with 'ssh: handshake failed' error during provisioning
  • Manual SSH connection works but Terraform remote-exec fails
  • File provisioner cannot upload/download files to newly created instances
  • Error mentions 'unable to authenticate, attempted methods [none publickey]'
  • SSH connection works after provisioning completes (timing issue)
  • Works with one key type (RSA) but fails with another (ed25519 or ECDSA)
  • Connection error: 'dial tcp {host}:22: connect: connection refused' followed by handshake failure

Common causes

  1. Private key path in connection block is incorrect or file doesn't exist
  2. Using deprecated 'key_file' argument instead of 'private_key'
  3. Private key file permissions are too permissive (should be 0600 or 400)
  4. SSH user doesn't match the AMI/image default user (e.g., using root instead of ubuntu, ec2-user, centos)
  5. Instance not fully initialized when Terraform attempts to connect
  6. SSH key pair on the instance doesn't match the private key being used
  7. SSH key type mismatch: Terraform sending ed25519 but server only accepts RSA (or vice versa)
  8. Security group or firewall not allowing SSH port 22 access
  9. SSH agent conflicts when agent = true is set but agent isn't running
  10. Host key verification issues or known_hosts verification enabled
  11. Connection timeout too short for instance to become ready
  12. Hostname/IP address in connection block is incorrect

How to fix "Error: ssh: handshake failed"

1Verify SSH works manually first

Before troubleshooting Terraform, confirm you can SSH to the instance manually:

bash
ssh -i /path/to/private/key.pem ubuntu@instance-ip-address

If this works, the instance and firewall are properly configured. The issue is in your Terraform connection block configuration.

If this fails with the same handshake error, the problem is with your SSH setup, not Terraform specifically.

2Use the correct private_key argument in connection block

Terraform requires the private_key argument (not the deprecated key_file). Load it using the file() function:

hcl
resource "aws_instance" "example" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t2.micro"
  key_name      = aws_key_pair.deployer.key_name

  connection {
    type        = "ssh"
    user        = "ubuntu"  # or ec2-user, centos, etc - depends on AMI
    private_key = file("~/.ssh/my-key.pem")  # CORRECT
    # NOT: key_file = "~/.ssh/my-key.pem"  # WRONG - deprecated
    host        = self.public_ip
  }

  provisioner "remote-exec" {
    inline = [
      "echo 'Connected!'"
    ]
  }
}

The file() function reads the key from disk. You can also use variables:

hcl
variable "private_key" {
  type      = string
  sensitive = true
}

connection {
  type        = "ssh"
  user        = "ubuntu"
  private_key = var.private_key
  host        = self.public_ip
}
3Match the SSH user to your AMI

Different cloud images have different default users. Using the wrong user causes handshake failures:

- Ubuntu (AWS): ubuntu
- Amazon Linux 2: ec2-user
- CentOS: centos
- RHEL (AWS): ec2-user
- Debian: admin
- Fedora: ec2-user
- Windows Server: N/A - use WinRM, not SSH

Example for different images:

hcl
# Ubuntu AMI
connection {
  type        = "ssh"
  user        = "ubuntu"  # Ubuntu's default user
  private_key = file("~/.ssh/key.pem")
  host        = self.public_ip
}

# Amazon Linux 2
connection {
  type        = "ssh"
  user        = "ec2-user"  # Amazon Linux's default user
  private_key = file("~/.ssh/key.pem")
  host        = self.public_ip
}

To find the correct user for an AMI, check the AMI description in the AWS console or AWS documentation.

4Ensure private key file permissions are correct

SSH is very strict about key file permissions. Keys with overly permissive permissions will be rejected:

bash
# Check current permissions
ls -la ~/.ssh/my-key.pem

# Set correct permissions (read/write for owner only)
chmod 600 ~/.ssh/my-key.pem

# Or for older systems
chmod 400 ~/.ssh/my-key.pem

The private key file should be owned by your user and readable only by the owner. If you see permissions like -rw-rw-r-- or -rw-r--r--, SSH will refuse to use it.

5Add a timeout to allow instance initialization

Newly created instances need time to fully boot and start the SSH daemon. Add a timeout and optional delay:

hcl
resource "aws_instance" "example" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t2.micro"
  key_name      = aws_key_pair.deployer.key_name

  connection {
    type        = "ssh"
    user        = "ubuntu"
    private_key = file("~/.ssh/key.pem")
    host        = self.public_ip

    # Allow up to 2 minutes for the instance to be ready
    timeout = "2m"
  }

  # Optional: wait for instance status checks to pass
  provisioner "remote-exec" {
    inline = [
      "echo 'Instance is ready'"
    ]
  }

  depends_on = [aws_instance.example]
}

# Or add an explicit wait
resource "time_sleep" "wait_for_instance" {
  depends_on = [aws_instance.example]

  create_duration = "30s"
}

resource "aws_instance" "example" {
  # ... configuration ...

  provisioner "remote-exec" {
    inline = ["echo 'Instance ready'"]
  }

  depends_on = [time_sleep.wait_for_instance]
}
6Check security group allows SSH port 22

Ensure the security group attached to the instance allows inbound SSH traffic:

hcl
resource "aws_security_group" "allow_ssh" {
  name = "allow_ssh"

  ingress {
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["YOUR_IP/32"]  # Restrict to your IP for security
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

resource "aws_instance" "example" {
  # ... other configuration ...
  vpc_security_group_ids = [aws_security_group.allow_ssh.id]
}

Warning: Only use 0.0.0.0/0 for testing. In production, restrict SSH to specific IPs.

7Try disabling SSH agent and host key verification

Sometimes SSH agent conflicts or host key verification issues cause handshake failures. Try disabling both:

hcl
connection {
  type            = "ssh"
  user            = "ubuntu"
  private_key     = file("~/.ssh/key.pem")
  host            = self.public_ip

  # Disable SSH agent to avoid conflicts
  agent           = false

  # Skip host key verification (less secure, but useful for testing)
  # This adds the host to known_hosts automatically
  host_key        = "ignore"  # Terraform 1.2+
}

Note: Disabling host key verification reduces security. Only use for development/testing. In production, add the host to your known_hosts file properly.

8Verify the key pair matches between local and remote

Ensure the public key on the remote instance matches your private key:

bash
# Extract the public key from your private key
ssh-keygen -y -f ~/.ssh/my-key.pem

# On the remote instance, check authorized_keys
ssh -i ~/.ssh/my-key.pem ubuntu@instance-ip
cat ~/.ssh/authorized_keys

The public key output from ssh-keygen -y must appear in the instance's ~/.ssh/authorized_keys file.

When using Terraform key pairs, ensure the correct key is specified:

hcl
resource "aws_key_pair" "deployer" {
  key_name   = "deployer-key"
  public_key = file("~/.ssh/my-key.pub")
}

resource "aws_instance" "example" {
  key_name = aws_key_pair.deployer.key_name
}
9Test with explicit SSH key type

Some systems have issues with certain SSH key types. Try testing with different key types:

bash
# Generate an RSA key (most compatible)
ssh-keygen -t rsa -b 4096 -f ~/.ssh/terraform-rsa.pem -N ""

# Or ed25519 (more modern, smaller)
ssh-keygen -t ed25519 -f ~/.ssh/terraform-ed25519.pem -N ""

# Or ECDSA
ssh-keygen -t ecdsa -b 521 -f ~/.ssh/terraform-ecdsa.pem -N ""

Update your Terraform to use the specific key:

hcl
connection {
  type        = "ssh"
  user        = "ubuntu"
  private_key = file("~/.ssh/terraform-rsa.pem")  # Try different keys
  host        = self.public_ip
}

If one key type works but others don't, the issue is key type compatibility between your SSH client and the server.

10Enable debug logging to see details

Use Terraform debug logging to see detailed connection information:

bash
# Enable debug logging
export TF_LOG=DEBUG

# Run your Terraform command
terraform apply

# Save to a file for easier analysis
export TF_LOG_PATH=./terraform-debug.log
terraform apply

# Disable when done
unset TF_LOG
unset TF_LOG_PATH

The debug logs will show:
- Which connection parameters are being used
- What SSH methods are being attempted
- Whether the connection succeeded or where it failed

Look for lines containing "handshake" or "ssh:" in the output.

Advanced notes

Understanding SSH authentication methods:

SSH supports several authentication methods. The error message "attempted methods [none publickey]" means Terraform tried none and publickey. If publickey fails, the handshake is aborted. Other methods like password or keyboard-interactive aren't even attempted.

Key type compatibility issues:

Different versions of SSH implementations have varying support for key types:
- RSA (ssh-rsa): Widely supported but older
- ed25519: Modern, secure, but not supported on very old systems
- ECDSA: Good balance, supported on most modern systems

The issue "key type ssh-rsa not in PubkeyAcceptedKeyTypes" means the server has disabled RSA keys for security reasons (common in recent OpenSSH versions). Switch to ed25519 or ECDSA.

SSH agent conflicts:

When agent = true (default), Terraform uses your SSH agent. If the agent:
- Isn't running
- Has different keys than expected
- Has keys with passphrase protection

...it can cause authentication failures. Setting agent = false forces Terraform to use only the specified private_key.

Host key verification:

By default, SSH verifies that the remote host's key hasn't changed (protection against MITM attacks). This requires:
1. The host key in your ~/.ssh/known_hosts file, OR
2. Host key verification disabled (less secure)

When connecting to new instances that aren't in known_hosts, you might see different errors depending on your SSH configuration. Terraform usually handles this by accepting the key automatically on first connection.

Provisioner best practices:

While provisioners work, the Terraform team recommends alternatives:
- Use user data scripts instead of remote-exec for most cases
- Use configuration management tools (Ansible, Chef, Puppet) for complex setup
- Consider cloud-init or user data for initial configuration

Provisioners should be a last resort because they make Terraform harder to reason about (imperative vs declarative), hide infrastructure setup, and are harder to troubleshoot.

For Windows instances:

SSH handshake errors on Windows instances are often due to Windows requiring WinRM instead of SSH. Use the winrm connection type instead. Windows usually requires Sysprep or a custom AMI with WinRM enabled, and firewall rules allowing port 5985/5986.

Frequently asked questions

What does "Error: ssh: handshake failed" mean?
The "ssh: handshake failed" error indicates that while Terraform was able to initiate a TCP connection to the remote host on port 22, the SSH protocol handshake and authentication phase failed. This error commonly appears in several contexts: - Terraform remote-exec provisioner: When running commands on newly created instances via SSH - Terraform file provisioner: When uploading or downloading files via SCP (which uses SSH) - SSH connection block: When configuring how Terraform should communicate with a resource The handshake failure typically has one of two root causes: 1. Authentication failed: The provided credentials (key or password) don't match what the remote host expects 2.
How do I fix "Error: ssh: handshake failed"?
The SSH handshake failed error occurs when Terraform's remote-exec or file provisioner cannot establish a successful SSH connection to a remote host. This is typically due to authentication configuration issues, incorrect credentials, key type mismatches, or timing problems during instance initialization.
What causes "Error: ssh: handshake failed"?
Private key path in connection block is incorrect or file doesn't exist Using deprecated 'key_file' argument instead of 'private_key' Private key file permissions are too permissive (should be 0600 or 400)
Is the "Error: ssh: handshake failed" error serious?
It is a medium-severity error — more of a blocker than a risk. The steps above resolve it.

Official resources & further reading