TechEarl

How to SSH into an AWS EC2 Instance

Connect to an EC2 instance four ways: plain SSH with a key pair, EC2 Instance Connect, Session Manager, and EC2 Instance Connect Endpoint. Default usernames, security group rules, and the troubleshooting matrix that fixes Permission denied and Connection timed out.

Ishan KarunaratneIshan Karunaratne⏱️ 19 min readUpdated
Connect to an AWS EC2 instance using plain SSH with a key pair, EC2 Instance Connect, AWS Systems Manager Session Manager, or an EC2 Instance Connect Endpoint for private instances. Default usernames, security group rules, and troubleshooting Permission denied and Connection timed out.

An EC2 instance is a Linux box listening on TCP/22 (or, optionally, no port at all if you use Session Manager). AWS gives you four sanctioned ways in. The fundamental method is plain ssh -i key.pem user@public_ip against a key pair, which works from any machine with OpenSSH and a route to the instance. The other three (EC2 Instance Connect, Session Manager, and EC2 Instance Connect Endpoint) trade local key management for IAM-driven access and, in the case of the last two, no public IP at all. The cluster of problems people hit is always the same: wrong username for the AMI, security group does not allow your IP, key file permissions too loose, or the instance has no public IP and they did not realize SSH would never work from the open internet. Below: the exact procedure for each method, default usernames by AMI, the ~/.ssh/config block I use for AWS, and a troubleshooting matrix that pins down Permission denied (publickey) versus Connection timed out in under a minute.

How do I SSH into an AWS EC2 instance?

Download your key pair .pem file when you launch the instance, then set permissions: chmod 400 ~/.ssh/aws_keypair.pem. From the EC2 console, grab the instance's public IPv4 address or public DNS name. Confirm the security group attached to the instance allows inbound TCP/22 from your source IP (ideally your /32, not 0.0.0.0/0). Connect with ssh -i ~/.ssh/aws_keypair.pem ec2-user@PUBLIC_IP. The username depends on the AMI: ec2-user for Amazon Linux 2 and Amazon Linux 2023, ubuntu for Ubuntu, admin for Debian, centos for CentOS, fedora for Fedora, ec2-user or rocky for Rocky Linux. If you cannot expose port 22 or assign a public IP, use AWS Systems Manager Session Manager (aws ssm start-session --target i-XXX) or an EC2 Instance Connect Endpoint. Both work via IAM, no inbound rules required.

Try it with your own values

Jump to:

The four ways to connect

AWS supports four sanctioned paths to a shell on an EC2 instance. Pick by network model and operational requirements.

MethodNetwork requirementAuthWhen to use
A. Plain SSH (key pair)Public IP plus inbound TCP/22Key pairThe default, scriptable, works everywhere
B. EC2 Instance ConnectPublic IP plus inbound TCP/22 from AWS service rangeIAM, ephemeral keyAd-hoc browser or CLI access without managing keys
C. Session ManagerNo inbound rules; outbound HTTPS to SSM endpointsIAMPrivate instances, audit-logged sessions, no SSH at all
D. EC2 Instance Connect EndpointNo public IP; endpoint in same VPCIAMSSH or SCP to private instances without a bastion

Methods C and D are how a hardened production environment looks. Method A is how a fresh AWS account looks. Method B is what the console's "Connect" button does behind the scenes.

Default usernames by AMI

The single most common reason ssh fails on a freshly launched EC2 instance is the wrong username. AWS does not let you log in as root over SSH on stock public AMIs; you log in as a sudo-capable initial user that is hardcoded into the AMI.

AMIDefault username
Amazon Linux 2023ec2-user
Amazon Linux 2ec2-user
Amazon Linux (legacy)ec2-user
Ubuntuubuntu
Debianadmin
CentOScentos
Rocky Linuxrocky or ec2-user (depends on AMI)
Fedorafedora or ec2-user
RHELec2-user (RHEL 7.4+) or root (older)
SUSEec2-user or root
Bitnami AMIsbitnami

If you launched a community AMI or a marketplace image, the username is in the AMI description on the launch page. Save it somewhere; you will need it again.

Method A: Plain SSH with a key pair

The original, the default, and still the workhorse.

Step 1: Get the key pair .pem file.

You either downloaded it at instance launch (the only chance to download a key pair that AWS generates for you) or you imported a public key you generated yourself with ssh-keygen. AWS stores only the public half; the private half is on your machine.

bash
chmod 400 :key_file

OpenSSH refuses to use a key file with looser permissions than 0400 (read-only by owner). Skipping this step yields WARNING: UNPROTECTED PRIVATE KEY FILE! and the connection aborts.

Step 2: Find the instance's public IPv4 or DNS name.

EC2 console, Instances, click the instance, the Public IPv4 address or Public IPv4 DNS field. The CLI version:

bash
aws ec2 describe-instances \
  --region :region \
  --instance-ids :instance_id \
  --query "Reservations[].Instances[].PublicIpAddress" \
  --output text

For workloads that need a stable IP, allocate an Elastic IP and associate it with the instance. Otherwise the IP changes every stop/start cycle.

Step 3: Confirm the security group allows your IP on port 22.

This is the second-most-common reason connections fail. Inbound rule needs to be TCP, port 22, source either 0.0.0.0/0 (open internet, bad) or your IP as a /32 (good).

bash
aws ec2 authorize-security-group-ingress \
  --region :region \
  --group-id sg-0abc123 \
  --protocol tcp \
  --port 22 \
  --cidr "$(curl -s https://checkip.amazonaws.com)/32"

That one-liner adds an inbound rule for your current public IP. Re-run it whenever your home or office IP changes. For a fuller treatment of the curl tricks above, the curl cheat sheet covers the option reference.

Step 4: Connect.

bash
ssh -i :key_file :user@:host

That is the whole thing. On first connection OpenSSH prompts to accept the host key; type yes. After that, ssh-keygen records the fingerprint in ~/.ssh/known_hosts and subsequent connects are silent.

To copy a file in either direction, swap ssh for scp or rsync:

bash
scp -i :key_file ./localfile.tar.gz :user@:host:/tmp/
rsync -avz -e "ssh -i :key_file" ./data/ :user@:host:/srv/data/

Method B: EC2 Instance Connect

EC2 Instance Connect (launched June 2019) sidesteps key-pair management for ad-hoc access. AWS pushes a short-lived public key into the instance's authorized_keys for 60 seconds, you connect, the key disappears.

Browser path: EC2 console, Instances, select the instance, Connect button, EC2 Instance Connect tab, Connect. A browser-based terminal opens. This is the easiest path for a one-off poke at an instance from a machine that does not have the .pem file.

CLI path: with AWS CLI v2.13.0+, a single command does everything:

bash
aws ec2-instance-connect ssh --region :region --instance-id :instance_id

The CLI generates a one-shot key pair, pushes the public half via the EC2 Instance Connect API, then invokes ssh for you. The connecting IAM principal needs ec2-instance-connect:SendSSHPublicKey on the instance.

Requirements:

  • The instance must have the ec2-instance-connect package installed (default on Amazon Linux 2, Amazon Linux 2023, Ubuntu 16.04+).
  • Inbound TCP/22 in the security group, either from your IP or from the AWS service IP range for EC2 Instance Connect in the instance's region (look up EC2_INSTANCE_CONNECT in the AWS IP ranges JSON).
  • Public IP on the instance (or use Method D for private instances).

EC2 Instance Connect is the "I lost the key pair and need in right now" escape hatch.

Method C: Session Manager (no SSH, no public IP)

AWS Systems Manager Session Manager (launched September 2018) is the most production-friendly option. The instance runs the SSM agent, which polls SSM endpoints over outbound HTTPS. There is no inbound rule on port 22, no public IP required, no key pair. Every session is logged to CloudWatch Logs or S3 if you want it.

Prerequisites:

  1. SSM agent installed on the instance. Amazon Linux 2, Amazon Linux 2023, and Ubuntu AMIs from 2017 onward ship it pre-installed.
  2. An IAM instance profile attached with the AmazonSSMManagedInstanceCore managed policy.
  3. Network reachability to SSM endpoints. Either a public IP plus internet gateway, a NAT gateway, or VPC endpoints for ssm, ssmmessages, and ec2messages.
  4. The Session Manager plugin installed on your local CLI.

Connect:

bash
aws ssm start-session --region :region --target :instance_id

That is it. A shell opens. Behind the scenes the SSM agent on the instance opens a bidirectional channel to AWS, your CLI joins the same channel, and the two sides pipe stdin/stdout. No port 22, no public IP, no SSH client involved.

SSH-over-SSM is also possible if you want to keep using ssh and scp against private instances:

code
# in ~/.ssh/config
Host i-* mi-*
    ProxyCommand sh -c "aws ssm start-session --target %h --document-name AWS-StartSSHSession --parameters 'portNumber=%p'"

With that block, ssh ec2-user@i-0abc123def456 works for any instance the IAM principal can ssm:StartSession against. The instance still needs a key pair if you go this route, since SSH does the actual auth; SSM only provides the tunnel.

Audit log: every command in a Session Manager session can be logged. For regulated environments this beats SSH, where session contents are not centrally captured.

Method D: EC2 Instance Connect Endpoint

EC2 Instance Connect Endpoint (GA June 2023) is the bastion-host replacement. It is a VPC-attached service that brokers SSH and RDP traffic from your IAM-authenticated CLI to private instances without those instances ever needing a public IP.

Setup is one-time:

bash
aws ec2 create-instance-connect-endpoint \
  --region :region \
  --subnet-id subnet-0abc123 \
  --security-group-ids sg-0abc123

The endpoint's security group must allow outbound TCP/22 to the target instance's security group. The target instance's security group must allow inbound TCP/22 from the endpoint's security group.

Connect:

bash
aws ec2-instance-connect ssh \
  --region :region \
  --instance-id :instance_id \
  --connection-type eice

Or for the long-form SSH-with-tunnel pattern:

bash
aws ec2-instance-connect open-tunnel \
  --region :region \
  --instance-id :instance_id \
  --local-port 2222 &
ssh -i :key_file -p 2222 :user@127.0.0.1

The tunnel routes through Instance Connect Endpoint, the instance's private IP is reached over VPC routing, and your IAM identity gates access.

This is the modern replacement for a Linux bastion EC2 instance running 24/7. No bastion to patch, no inbound rules on internet-facing security groups.

Security group rules for SSH

The single inbound rule that lets you SSH:

TypeProtocolPort rangeSource
SSHTCP220.0.0.0/0 (open internet) or YOUR_IP/32 (locked down)

Use your /32 whenever possible. A wide-open port 22 on the internet receives tens of thousands of brute-force attempts per day, which fail against key-based auth but fill the logs and waste CPU.

For a fleet, the cleanest pattern is a "bastion" security group with the inbound /32 rule, attached to any instance you want SSH access to. The bastion mental model is now mostly obsolete (Session Manager and EC2 Instance Connect Endpoint do it better) but the security group pattern persists.

To lock down an existing SG to your current IP, revoke the open rule and add the narrow one:

bash
MY_IP=$(curl -s https://checkip.amazonaws.com)
aws ec2 revoke-security-group-ingress --region :region \
  --group-id sg-0abc123 --protocol tcp --port 22 --cidr 0.0.0.0/0
aws ec2 authorize-security-group-ingress --region :region \
  --group-id sg-0abc123 --protocol tcp --port 22 --cidr "${MY_IP}/32"

The ~/.ssh/config block I use for EC2

code
Host ec2-bastion
    HostName 54.197.100.42
    User ec2-user
    IdentityFile ~/.ssh/aws_keypair.pem
    IdentitiesOnly yes
    ServerAliveInterval 60
    ServerAliveCountMax 3

Host ec2-*
    User ec2-user
    IdentityFile ~/.ssh/aws_keypair.pem
    IdentitiesOnly yes
    StrictHostKeyChecking accept-new

Host i-* mi-*
    ProxyCommand sh -c "aws ssm start-session --target %h --document-name AWS-StartSSHSession --parameters 'portNumber=%p'"
    User ec2-user
    IdentityFile ~/.ssh/aws_keypair.pem
    IdentitiesOnly yes

The first block names a specific bastion. The second handles anything I name with an ec2-* prefix in my local DNS or /etc/hosts. The third makes ssh ec2-user@i-0abc123def456 work via Session Manager for private instances.

IdentitiesOnly yes matters when you have multiple keys in ssh-agent. Without it, OpenSSH offers every key in agent before falling back to the right one, and AWS will start rejecting attempts before you reach the correct key. For the broader SSH option reference, the SSH cheat sheet has the flag list.

Comparison: Console vs CLI vs Session Manager vs IAP

The same job, multiple interfaces.

ToolBest forKey managementNetwork modelAudit log
EC2 console Connect buttonOne-off browser accessNone (Instance Connect)Public IP plus SG inboundCloudTrail only
Plain OpenSSH (Method A)Scripted automation, CIManual .pemPublic IP plus SG inboundNone on the session itself
aws ec2-instance-connect sshAd-hoc CLI access, no .pemEphemeral, IAM-issuedPublic IP plus SG inboundCloudTrail per push
Session ManagerProduction, regulated environmentsNoneOutbound HTTPS to SSMFull session log (optional)
EC2 Instance Connect EndpointPrivate instances, replace bastion.pem or ephemeralNo public IP neededCloudTrail per session

For a single-person AWS account doing personal infrastructure, plain SSH with a key pair plus a tight security group is fine. For a team, lean on Session Manager or Instance Connect Endpoint and stop managing .pem files across laptops.

For batch operations against many instances, a Bash for loop over an instance-ID list with ssh or aws ssm start-session is the simplest pattern. Pair with a Bash while loop and aws ec2 describe-instance-status if you need to wait for instances to reach running before connecting.

Troubleshooting: Permission denied, Connection timed out

The five errors that cover 99% of real failures.

Permission denied (publickey)

Network works, authentication does not. In order of frequency:

  • Wrong username. This is almost always the cause. ec2-user for Amazon Linux, ubuntu for Ubuntu, admin for Debian. See the Default usernames by AMI section. Run ssh -v and look for Authenticated to ... using "publickey" versus Permission denied (publickey).
  • Wrong key. ssh -v shows Offering public key: lines. Confirm the key matches the key pair name attached to the instance (aws ec2 describe-instances ... --query "...KeyName").
  • Key permissions too loose. chmod 400 ~/.ssh/aws_keypair.pem.
  • Custom AMI with sshd locked down. PasswordAuthentication no plus a missing ~/.ssh/authorized_keys for the user. Recover via Session Manager.

ssh: connect to host X.X.X.X port 22: Connection timed out (or Operation timed out)

Network-layer problem. The TCP SYN is being dropped before reaching sshd.

  • Security group does not allow TCP/22 from your IP. Most common cause. Add the rule (see the security group section).
  • NACL on the subnet blocks the port. Rare on default VPCs; check the network ACL.
  • The instance is in a private subnet with no public IP. Internet cannot reach it. Switch to Session Manager or Instance Connect Endpoint.
  • The instance is stopped. Check the instance state in the console.

ssh: connect to host X.X.X.X port 22: Connection refused

Network works, but nothing is listening on port 22.

  • sshd crashed or was disabled. Use Session Manager to recover, or use the EC2 Serial Console.
  • The instance just rebooted and sshd has not finished starting. Wait 30 seconds.

Host key verification failed

The instance's host key changed since you last connected. Usually because the instance was terminated and a new one was launched with the same IP.

bash
ssh-keygen -R :host

Removes the stale entry from ~/.ssh/known_hosts. Then reconnect and accept the new key.

WARNING: UNPROTECTED PRIVATE KEY FILE!

OpenSSH refusing to use a key with too-loose permissions.

bash
chmod 400 :key_file

The full debug recipe: ssh -vvv -i ~/.ssh/aws_keypair.pem ec2-user@PUBLIC_IP 2>&1 | less. The Offering public key: and Server accepts key: lines pin down auth issues; the connect to address line pins down network issues.

What to do next

FAQ

TagsAWSEC2SSHOpenSSHSession ManagerEC2 Instance ConnectIAMLinux
Share
Ishan Karunaratne

Ishan Karunaratne

Tech Architect · Software Engineer · AI/DevOps

Tech architect and software engineer with 20+ years across software, Linux systems, DevOps, and infrastructure — and a more recent focus on AI. Currently Chief Technology Officer at a tech startup in the healthcare space.

Keep reading

Related posts

Create an EBS volume with aws ec2 create-volume, attach it to a running EC2 instance, format with mkfs.ext4 or mkfs.xfs, mount it, and persist across reboots with a UUID-based /etc/fstab entry. Console, AWS CLI, and Terraform walkthroughs.

How to Add an EBS Volume to an EC2 Instance

Create an EBS volume, attach it to a running EC2 instance, format and mount it, and survive reboots with a UUID-based fstab entry. Console, AWS CLI, and Terraform walkthroughs plus the Nitro device-naming gotcha that trips everyone.

Change an EC2 instance type without data loss: stop the instance, run aws ec2 modify-instance-attribute, start it again. Covers Nitro vs Xen compatibility, ENA and NVMe driver requirements, the instance-store ephemeral-data trap, and the zero-downtime ASG rolling-replace pattern.

How to Change an AWS EC2 Instance Type (Resize Without Data Loss)

Stop the instance, modify the instance type, start it. The exact gcloud-equivalent AWS CLI syntax, the compatibility matrix for moving between families and generations, the Nitro vs Xen gotcha, the instance-store data-loss trap, and the production sequence (snapshot AMI, scale out, replace) that gets you to a new instance type with zero downtime.

Connect to a GCP Compute Engine VM with plain OpenSSH and no gcloud CLI. Add a public key via instance metadata, ssh to the external IP, configure ~/.ssh/config, plus OS Login and IAP.

How to SSH into a Google Cloud VM Without gcloud

Connect to a GCP VM using plain OpenSSH, no gcloud required. Add a public key to instance metadata, fetch the external IP, and ssh in like any normal Linux box. Plus OS Login, IAP, and a Windows PuTTY path.