Skip to content

Fix: AWS EC2 SSH Connection Refused or Timed Out

FixDevs · (Updated: )

Part of:  Docker, DevOps & Infrastructure

Quick Answer

How to fix AWS EC2 SSH connection refused or timed out errors — security group rules, key pair issues, sshd not running, wrong username, and network ACL misconfigurations.

The Error

You try to SSH into an EC2 instance and get:

ssh: connect to host ec2-12-34-56-78.compute-1.amazonaws.com port 22: Connection refused

Or:

ssh: connect to host 12.34.56.78 port 22: Operation timed out

Or:

[email protected]: Permission denied (publickey).

Or after connecting briefly, the connection drops:

packet_write_wait: Connection to 12.34.56.78 port 22: Broken pipe

Why This Happens

SSH to EC2 fails for several distinct reasons with different symptoms. Reading the exact error message is the fastest way to narrow down which layer is broken:

  • Connection refused (immediate): port 22 is reachable but nothing is listening — sshd is not running, or the instance is still booting.
  • Connection timed out (after ~75 seconds): the packet never reaches the instance — security group, network ACL, or routing issue.
  • Permission denied (publickey): connected successfully but authentication failed — wrong key, wrong username, or authorized_keys corrupted.
  • No route to host: the instance has no public IP, is in a private subnet without a NAT/bastion, or the VPC routing is broken.

The most common confusion is between “Connection refused” and “Operation timed out.” A refused connection means the TCP SYN reached the host and the host responded with RST — sshd is down, on a different port, or blocked at the host firewall (not security group). A timeout means the SYN was dropped by a network policy somewhere before reaching the instance. Different errors require different fixes.

A second source of confusion is that EC2 has two network filtering layers (security groups and NACLs) plus the OS-level firewall (iptables/firewalld), and any one of them can silently drop traffic. AWS Security Groups are stateful (return traffic is auto-allowed), but NACLs are stateless and require explicit outbound rules for the ephemeral ports SSH uses for the reply path. Many SSH failures come from a NACL that someone tightened months ago.

Platform and Environment Differences

EC2 SSH behavior varies depending on the AMI, the instance metadata service version, the network architecture, and how you authenticate. Knowing the defaults for your environment saves hours of guessing.

VPC Security Groups vs Network ACLs. Security Groups are stateful and instance-attached — return traffic is automatically allowed. NACLs are stateless and subnet-attached — you must explicitly allow inbound and outbound traffic, including the ephemeral port range (1024-65535) for return packets. If only one subnet’s instances fail to accept SSH, suspect a NACL, not a Security Group. The default NACL allows everything; custom NACLs created via Terraform/CDK often miss the ephemeral outbound rule.

IMDSv2 vs IMDSv1. Instance Metadata Service v2 (token-based) is the default on all instances launched after mid-2024. Older AMIs and scripts that fetch http://169.254.169.254/latest/meta-data/ without a token will fail. If your cloud-init or user-data script that sets up SSH keys uses IMDSv1, it silently fails on new launches — instance comes up but with no keys. Either request a token first (curl -X PUT -H "X-aws-ec2-metadata-token-ttl-seconds: 21600" http://169.254.169.254/latest/api/token) or set HttpTokens: optional in the launch template’s MetadataOptions.

Default SSH config per AMI.

  • Amazon Linux 2023 (AL2023): sshd runs by default, accepts only key auth, default user ec2-user. Uses dnf and ships modern OpenSSH.
  • Amazon Linux 2 (AL2): Same defaults; reaches end of support November 2026 — schedule migrations.
  • Ubuntu 22.04/24.04: Default user ubuntu, sshd runs by default. UFW disabled by default, but some Canonical hardened images enable it.
  • Debian 12: Default user admin or debian depending on the AMI publisher.
  • RHEL 9: Default user ec2-user. SELinux enforcing by default — chmod issues on ~/.ssh cause silent auth failures with confusing log entries.
  • Bitnami / community AMIs: Often use bitnami, centos, or a service-specific username.

EC2 Instance Connect vs Session Manager vs traditional SSH.

  • Traditional SSH: requires inbound 22 from your IP, a key pair, and a route from internet/bastion.
  • EC2 Instance Connect: AWS pushes a temporary public key (60s validity) into the instance, then you connect via SSH from your browser or the mssh CLI. Requires the ec2-instance-connect package installed (preinstalled on AL2/AL2023) and the SG to allow port 22 from AWS’s EC2 Instance Connect IP range or 0.0.0.0/0.
  • Session Manager (SSM): connects via the SSM agent (preinstalled on AL2/AL2023/Ubuntu 18.04+) over an outbound HTTPS tunnel — no inbound port 22 required. Needs the instance IAM role to include AmazonSSMManagedInstanceCore. This is the recommended path for private-subnet instances.

If traditional SSH is failing, Session Manager is your fallback because it requires no inbound rules.

Regional outages and infrastructure issues. A small percentage of “SSH timeouts” are actually AWS-side problems: a partial region outage, a degraded availability zone, or a control-plane issue affecting the metadata service. Always check the AWS Health Dashboard before deep-diving. EC2 instances in a degraded AZ can show “running” but be unreachable from outside the AZ.

Public IPv4 cost change (Feb 2024). AWS now charges for every public IPv4, including ones attached to running instances. Some teams responded by removing public IPs en masse; if SSH stopped working after a billing cleanup, your instance lost its public IP. Use Session Manager or a bastion in the same VPC.

Private subnets and NAT. Instances in a private subnet (no internet gateway in the route table) cannot be reached directly from the internet. SSH them via:

  1. A bastion in a public subnet.
  2. Session Manager (no inbound port required).
  3. AWS Client VPN or Direct Connect.

Carrier-grade NAT and changing source IP. If you connected from coffee shop A this morning and home network B this afternoon, your 0.0.0.0/0 is fine but x.x.x.x/32 will break. Mobile networks often hand out a different IP every few minutes via CGNAT.

Fix 1: Check and Fix Security Group Rules

The most common cause of timeout: the security group blocking inbound SSH traffic on port 22.

Check security group rules in AWS Console:

  1. EC2 Console → Instances → select your instance.
  2. Security tab → click the Security Group link.
  3. Inbound rules tab → look for a rule allowing port 22.

Required rule:

TypeProtocolPortSource
SSHTCP22Your IP (x.x.x.x/32) or 0.0.0.0/0

Add the rule via AWS CLI:

# Get your current public IP
MY_IP=$(curl -s https://checkip.amazonaws.com)

# Add SSH rule for your IP only
aws ec2 authorize-security-group-ingress \
  --group-id sg-xxxxxxxxxxxxxxxxx \
  --protocol tcp \
  --port 22 \
  --cidr "${MY_IP}/32"

Find the security group ID:

aws ec2 describe-instances \
  --instance-ids i-xxxxxxxxxxxxxxxxx \
  --query "Reservations[0].Instances[0].SecurityGroups[*].GroupId" \
  --output text

Warning: Using 0.0.0.0/0 (all IPs) as the SSH source is a security risk — it exposes your instance to brute-force attacks from the internet. Always restrict SSH to your specific IP or a bastion host IP. Use x.x.x.x/32 (single IP) rather than a wide range.

Fix 2: Verify the Instance Has a Public IP and Is Running

# Check instance state and public IP
aws ec2 describe-instances \
  --instance-ids i-xxxxxxxxxxxxxxxxx \
  --query "Reservations[0].Instances[0].{State:State.Name,PublicIP:PublicIpAddress,PublicDNS:PublicDnsName}" \
  --output table
  • State must be “running” — a stopped or stopping instance cannot accept SSH.
  • PublicIP must not be null — instances in private subnets without an Elastic IP or public IP assignment cannot be reached directly from the internet.

If there is no public IP:

Option A: Associate an Elastic IP:

# Allocate an Elastic IP
aws ec2 allocate-address --domain vpc

# Associate it with the instance
aws ec2 associate-address \
  --instance-id i-xxxxxxxxxxxxxxxxx \
  --allocation-id eipalloc-xxxxxxxxxxxxxxxxx

Option B: Connect via a bastion host or AWS Systems Manager Session Manager (no public IP needed).

Fix 3: Use the Correct Username

Each AMI has a default SSH username. Using the wrong one gives Permission denied (publickey) even with the correct key:

AMI / DistributionDefault Username
Amazon Linux / Amazon Linux 2ec2-user
Amazon Linux 2023ec2-user
Ubuntuubuntu
Debianadmin or debian
CentOScentos
RHELec2-user or root
Fedorafedora
SUSEec2-user or root
# Amazon Linux
ssh -i ~/.ssh/my-key.pem [email protected]

# Ubuntu
ssh -i ~/.ssh/my-key.pem [email protected]

# Debian
ssh -i ~/.ssh/my-key.pem [email protected]

Check the AMI details to confirm:

aws ec2 describe-images \
  --image-ids ami-xxxxxxxxxxxxxxxxx \
  --query "Images[0].{Name:Name,Description:Description}" \
  --output table

Fix 4: Fix Key Pair Issues

Wrong key file:

# Specify the key explicitly
ssh -i /path/to/correct-key.pem [email protected]

# Or add to SSH config
# ~/.ssh/config
Host my-ec2
    HostName 12.34.56.78
    User ec2-user
    IdentityFile ~/.ssh/my-key.pem

Wrong permissions on the key file:

SSH refuses keys with insecure permissions:

chmod 400 ~/.ssh/my-key.pem
# Or: chmod 600 ~/.ssh/my-key.pem

If permissions are wrong, SSH shows:

WARNING: UNPROTECTED PRIVATE KEY FILE!
Permissions 0644 for 'my-key.pem' are too open.

Verify key pair matches the instance:

# Get the key pair name associated with the instance
aws ec2 describe-instances \
  --instance-ids i-xxxxxxxxxxxxxxxxx \
  --query "Reservations[0].Instances[0].KeyName" \
  --output text

Make sure this matches the key file you are using.

Fix 5: Fix Connection Timeouts — Check Network ACLs and Routing

Security groups are stateful (allow return traffic automatically). Network ACLs are stateless — they need both inbound and outbound rules. If your VPC has a custom Network ACL, it may be blocking SSH:

Check Network ACL rules in AWS Console:

VPC Console → Network ACLs → select the ACL associated with your subnet → Inbound/Outbound rules.

Required Network ACL rules for SSH:

DirectionRule #TypeProtocolPortSourceAction
Inbound100SSHTCP220.0.0.0/0ALLOW
Outbound100Custom TCPTCP1024-655350.0.0.0/0ALLOW

The outbound rule for ephemeral ports (1024–65535) is required because SSH responses go back on a random high port — Network ACLs do not track connection state.

Check route table:

VPC Console → Route Tables → select the route table for your subnet → Routes tab. There should be an entry:

0.0.0.0/0 → igw-xxxxxxxxxxxxxxxxx  (Internet Gateway)

Without an internet gateway route, traffic cannot leave or enter the subnet from the internet.

Fix 6: Fix sshd Not Running on the Instance

If the connection is refused immediately (not timed out), sshd is likely not running. Use EC2 Instance Connect or Systems Manager Session Manager to connect without SSH and fix it:

Using EC2 Instance Connect (browser-based terminal):

  1. EC2 Console → select instance → Connect button → EC2 Instance Connect tab → Connect.

This works even when SSH is broken, as long as the security group allows port 22 from AWS IP ranges (or use Session Manager which needs no inbound ports).

Using AWS Systems Manager Session Manager:

# Install Session Manager plugin locally
# Then:
aws ssm start-session --target i-xxxxxxxxxxxxxxxxx

Session Manager works without any open inbound ports — it connects via the AWS network.

Once connected, fix sshd:

# Check sshd status
sudo systemctl status sshd

# Start sshd if stopped
sudo systemctl start sshd
sudo systemctl enable sshd

# Check sshd config for errors
sudo sshd -t

# Restart after config changes
sudo systemctl restart sshd

Fix 7: Fix Intermittent Disconnections (Broken Pipe)

SSH connections that drop after a period of inactivity are caused by idle timeout settings on the instance or network:

Fix on the client side — keep-alive in ~/.ssh/config:

Host *
    ServerAliveInterval 60
    ServerAliveCountMax 3

This sends a keep-alive packet every 60 seconds, preventing the connection from being dropped by firewalls or NAT gateways that expire idle connections.

Fix on the server side — edit /etc/ssh/sshd_config:

sudo nano /etc/ssh/sshd_config

Add or update:

ClientAliveInterval 60
ClientAliveCountMax 3
TCPKeepAlive yes
sudo systemctl restart sshd

AWS NAT Gateway idle timeout: NAT Gateways drop idle TCP connections after 350 seconds. If you SSH through a NAT Gateway (e.g., from a bastion in a public subnet to instances in a private subnet), the keep-alive settings above prevent this.

Debug Checklist

Run through these in order:

# 1. Can you reach the instance at all?
ping 12.34.56.78

# 2. Is port 22 open and responding?
nc -zv 12.34.56.78 22
# Expected: Connection to 12.34.56.78 22 port [tcp/ssh] succeeded!

# 3. Verbose SSH output
ssh -vvv -i ~/.ssh/my-key.pem [email protected]
# Shows exactly where the connection fails

# 4. Try a different key explicitly
ssh -i ~/.ssh/other-key.pem -o IdentitiesOnly=yes [email protected]

The verbose output from ssh -vvv shows each step: DNS resolution, TCP connection, SSH handshake, authentication. The step where it fails points to the root cause.

Still Not Working?

Check instance system log for boot errors. If the instance is running but sshd crashed during boot, the system log shows the error:

aws ec2 get-console-output --instance-id i-xxxxxxxxxxxxxxxxx --output text

Check if the disk is full. A full root disk prevents sshd from creating the socket file. Connect via Session Manager and run df -h.

Check if authorized_keys was corrupted. If you can connect via Session Manager, verify:

cat ~/.ssh/authorized_keys
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys

For EBS-backed instances where you lost the key, detach the root volume, attach it to another instance, mount it, and fix authorized_keys — then reattach it to the original instance.

Check SELinux context on RHEL/AL2023. If authorized_keys was written by a different user or process (e.g., cloud-init that crashed mid-write), the SELinux label may be wrong. Connect via SSM and run:

restorecon -Rv /home/ec2-user/.ssh/
ls -lZ /home/ec2-user/.ssh/authorized_keys
# Should show: unconfined_u:object_r:ssh_home_t:s0

Check the IAM role has the SSM core policy. If Session Manager itself is failing with “the instance is not registered,” the instance role is missing AmazonSSMManagedInstanceCore. Attach it via the IAM console and wait 2-3 minutes for the SSM agent to pick up the new role.

Check for VPC endpoint conflicts. If the instance is in a fully private subnet with only VPC endpoints (no internet gateway), the SSM agent needs endpoints for ssm, ssmmessages, and ec2messages. Missing any one of these breaks Session Manager. See also AWS unable to locate credentials if the agent itself cannot authenticate.

Check for IAM permissions to start a session. Even with the instance role correct, your IAM user/role needs ssm:StartSession permission. If you get AccessDeniedException, that is your credentials, not the instance’s. See AWS IAM AccessDeniedException for the policy patterns.

Check the EC2 Connect Endpoint as a no-IP alternative. If you need to keep SSH tooling but cannot afford a public IP, EC2 Instance Connect Endpoint (launched 2023) lets you tunnel SSH to private instances without a bastion or public IP. Configure with aws ec2-instance-connect open-tunnel.

For general SSH issues not specific to AWS, see Fix: SSH Connection Timed Out and Fix: SSH Permission Denied (publickey).

F

FixDevs

Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.

Was this article helpful?

Related Articles