Ravindra BagaleCourses & study guides Track your progress

Guides

How to SSH into a Private EC2 Instance Through a Bastion Host (Jump Server)

To SSH into a private EC2 instance, go through a bastion host (jump server) in a public subnet, without copying your key to it. On your laptop run ssh-add mykey.pem, then ssh -J ec2-user@<BASTION_PUBLIC_IP> ec2-user@<PRIVATE_IP>. The private instance's security group must allow SSH (22) only from the bastion's security group, and the bastion's security group allows 22 only from your IP.

Come on, friends! To go into the society's inner wing, you first register your name at the reception near the main gate, then go in. The bastion host is that reception: first SSH to it, then from there to the private server. And note: keep the key (private key) in your own pocket; don't leave it at reception.

Quick answer

On your laptop (macOS, Linux, or Windows 10/11 with OpenSSH):

chmod 400 mykey.pem                          # macOS/Linux: the key must be private
ssh-add mykey.pem                            # load the key into the SSH agent
ssh -J ec2-user@<BASTION_PUBLIC_IP> ec2-user@10.0.2.10

Or with agent forwarding, in two hops:

ssh -A ec2-user@<BASTION_PUBLIC_IP>
ssh ec2-user@10.0.2.10                       # run on the bastion; the key stays on your laptop

What do I need before connecting?

  • A VPC with a public subnet (bastion) and a private subnet (target). See Public vs private subnet.
  • Two instances launched with the same key pair (or two keys, both loaded with ssh-add).
  • Security groups:
Security group Inbound rule Attached to
bastion-sg SSH 22 from My IP (e.g. 198.51.100.7/32) Bastion
app-sg SSH 22 from bastion-sg Private instance
  • The SSH agent running: on macOS and Linux it usually is; on Windows start it once in an admin PowerShell with Get-Service ssh-agent | Set-Service -StartupType Automatic; Start-Service ssh-agent.

How does SSH through a bastion work?

SSH to a private server through a bastion host with ssh -J The laptop runs ssh -J. The first SSH connection goes to the bastion host in the public subnet on port 22. From the bastion, the connection jumps to the app server 10.0.2.10 in the private subnet on port 22, which allows SSH only from bastion-sg. The private key stays on the laptop; it is never copied to the bastion. Laptop key stays here Public subnet Bastion bastion-sg: 22 from My IP Private subnet App10.0.2.10 app-sg: 22 from bastion-sg [ec2-user@ip-10-0-2-10 ~]$ ssh -J ec2-user@BASTION ec2-user@10.0.2.10 SSH :22

ssh -J first connects to the bastion in the public subnet on port 22, then jumps to the private server 10.0.2.10, which allows SSH only from bastion-sg. The private key stays on your laptop.

With ssh -J, your laptop opens an SSH connection to the bastion and, through it, a second SSH connection to the private server. Both logins are checked with the key held by the agent on your laptop; the bastion only passes the encrypted traffic along. Nothing secret is stored on the bastion.

How do I SSH into a private instance step by step?

Step 1 — Load your key into the agent

chmod 400 mykey.pem
ssh-add mykey.pem
ssh-add -l            # lists the loaded key

Step 2 — Jump with ProxyJump (-J)

ssh -J ec2-user@192.0.2.10 ec2-user@10.0.2.10

Replace 192.0.2.10 (a documentation example address) with the bastion's public IP and 10.0.2.10 with the private instance's private IP (EC2 console → instance → Private IPv4 addresses). Note: an -i mykey.pem option on this command applies only to the final server, not to the bastion — that is why we use ssh-add.

Host bastion
    HostName 192.0.2.10
    User ec2-user
    IdentityFile ~/keys/mykey.pem

Host app
    HostName 10.0.2.10
    User ec2-user
    IdentityFile ~/keys/mykey.pem
    ProxyJump bastion

Now ssh app is enough. Each host uses its own IdentityFile, so here ssh-add is not even required.

Step 4 — Or use agent forwarding (-A)

ssh -A ec2-user@192.0.2.10
ssh ec2-user@10.0.2.10

-A forwards the agent, so the second ssh on the bastion can use the key that lives on your laptop. Use it only with bastions you trust, because an administrator of the bastion could use your forwarded agent while you are connected. ssh -J avoids that and is the better default. 🔑

Ravindra Bagale's Tip

Some tutorials say: scp the .pem file to the bastion. Never! If the bastion is hacked, the key to your private servers is gone too. Keep the key on your laptop and use ssh-add and ssh -J. And on your laptop give the key chmod 400, otherwise SSH refuses to use it.

How do I fix bastion SSH errors?

Ghabru naka 😅 — these are the usual ones:

Symptom Likely cause Fix
Permission denied (publickey) on the private server Key not loaded in the agent, or you used -i (applies only to the final hop) ssh-​add mykey.​pem, check ssh-add -l, retry
Could not open a connection to your authentication agent Agent not running macOS/Linux: eval "$(ssh-​agent -​s)"; Windows: start the ssh-agent service
Timeout to the bastion bastion-sg does not allow 22 from your current IP Edit the rule to My IP again (home IPs change)
Timeout from bastion to the private IP app-sg has no SSH rule from bastion-sg Add inbound SSH 22, source bastion-sg
WARNING: UNPROTECTED PRIVATE KEY FILE! Key permissions too open chmod 400 mykey.​pem
Host key verification failed after rebuilding servers Old host key cached ssh-​keygen -​R 10.​0.​2.​10 (and the bastion IP)

Is there a way without a bastion?

Yes: AWS Systems Manager Session Manager opens a shell from the console or CLI with no inbound port 22 at all. The instance needs the SSM Agent (preinstalled on Amazon Linux 2023), an IAM role with the AmazonSSMManagedInstanceCore policy, and a way to reach the SSM service (a NAT gateway or VPC endpoints). EC2 Instance Connect Endpoint is another bastion-free option. The bastion is still worth learning: it is common in companies and in interviews.

Try it at home

Connect to a private instance with ssh -J, then write the ~/.ssh/config above and connect with just ssh app. Finally, try ssh -i mykey.pem -J ... without ssh-add after running ssh-add -D, read the error, and explain why it happens.

Got it? ssh-add, then ssh -J through the bastion to the private IP. The private server's port 22 only for bastion-sg. The key stays on the laptop – never on the bastion.

Frequently asked questions

How do I SSH into a private EC2 instance?

Through a bastion host in a public subnet: load the key with ssh-add mykey.pem and run ssh -J ec2-user@BASTION_IP ec2-user@PRIVATE_IP. The private instance must allow port 22 from the bastion security group.

Should I copy my .pem key to the bastion host?

No. If the bastion is compromised, the key to every private server is stolen. Keep the key on your laptop and use ssh -J or agent forwarding.

What is the difference between ssh -J and ssh -A?

-J (ProxyJump) tunnels the second connection through the bastion from your laptop. -A forwards your agent to the bastion so you can run ssh there. -J exposes less and is the better default.

Why do I get Permission denied (publickey) on the private server?

The key is not in your SSH agent, or you passed -i, which applies only to the final hop. Run ssh-add mykey.pem and check with ssh-add -l.

Which security group rules does a bastion setup need?

The bastion allows SSH 22 from your IP only. Private instances allow SSH 22 only from the bastion security group, not from 0.0.0.0/0.

Can I connect without a bastion?

Yes, with AWS Systems Manager Session Manager (IAM role, SSM Agent, no port 22) or EC2 Instance Connect Endpoint.