Skip to content
AWS aws networking 5 min read

Bastion Hosts

When you put servers in a private subnet (a subnet with no direct route to the internet), they have no public IP address, so you cannot SSH to them straight from your laptop. A bastion host (also called a “jump host”) is a single, hardened server that lives in a public subnet and acts as a controlled doorway: you SSH into the bastion, then hop from the bastion to your private instances. This page shows how to build one the classic way, and then shows the modern, safer alternative that most teams should use today.

What a bastion host is

A bastion host is just an Amazon EC2 (Elastic Compute Cloud, AWS’s virtual server service) instance with a public IP, placed in a public subnet, whose only job is to forward SSH (Secure Shell, an encrypted remote-login protocol) connections to instances that have no public access. Because it is exposed to the internet, you keep it as small and locked-down as possible: minimal software, the latest patches, and a security group that only allows SSH (TCP port 22) from your own IP range.

The traffic path looks like this:

Your laptop  --SSH-->  Bastion (public subnet, public IP)  --SSH-->  App server (private subnet, private IP)

When to use this (and when not)

ApproachOpen inbound ports?Audit log of sessionsExtra server to patchUse when
Bastion hostYes (port 22)No (unless you build it)YesLegacy tooling needs raw SSH/SCP; you cannot install the SSM agent
SSM Session ManagerNoYes (built in)NoNew systems, compliance needs, the recommended default
VPN / Direct ConnectNo public SSHPartialVPN applianceYou want your whole network reachable, not just one host

Recommendation: For new workloads in 2026, prefer AWS Systems Manager (SSM) Session Manager over a bastion. A bastion is one more internet-facing machine you must patch, monitor, and defend. See the Session Manager section below.

Building a bastion host

Console steps

  1. Open the EC2 console and choose Launch instance.
  2. Pick a small, current Amazon Linux 2023 image (an AMI, or Amazon Machine Image, the disk template for an instance), for example ami-0abcdef1234567890, and a t3.micro instance type.
  3. Under Network settings, select your VPC vpc-0a1b2c3d and a public subnet subnet-0a1b2c3d.
  4. Set Auto-assign public IP to Enable (or attach an Elastic IP, a permanent public IP address, afterward).
  5. Create a security group sg-0a1b2c3d named bastion-sg allowing SSH (port 22) only from your office IP, e.g. 203.0.113.10/32.
  6. Choose your key pair, then Launch instance.

CLI equivalent

aws ec2 run-instances \
  --image-id ami-0abcdef1234567890 \
  --instance-type t3.micro \
  --key-name my-key \
  --subnet-id subnet-0a1b2c3d \
  --security-group-ids sg-0a1b2c3d \
  --associate-public-ip-address \
  --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=bastion}]'

Output:

{
    "Instances": [
        {
            "InstanceId": "i-0a1b2c3d4e5f",
            "PublicIpAddress": "203.0.113.55",
            "SubnetId": "subnet-0a1b2c3d",
            "State": { "Name": "pending" }
        }
    ]
}

The private instance gets its own security group sg-0b2c3d4e that allows port 22 only from the bastion’s security group (sg-0a1b2c3d), not from the whole internet:

aws ec2 authorize-security-group-ingress \
  --group-id sg-0b2c3d4e \
  --protocol tcp --port 22 \
  --source-group sg-0a1b2c3d

SSH agent forwarding

The naive way to hop is to copy your private key onto the bastion — never do this, because anyone who breaks into the bastion then has your key. Instead use SSH agent forwarding, which lets the bastion borrow your local key for one hop without the key ever touching its disk.

# Load your key into the local agent once
ssh-add ~/.ssh/my-key.pem

# -A forwards the agent; jump to the private host through the bastion in one command
ssh -A -J [email protected] [email protected]

The -J (ProxyJump) flag tells SSH to first connect to the bastion, then tunnel through to the private IP 10.0.2.30. You can make it permanent in ~/.ssh/config:

Host bastion
    HostName 203.0.113.55
    User ec2-user
    ForwardAgent yes

Host app-private
    HostName 10.0.2.30
    User ec2-user
    ProxyJump bastion

Now ssh app-private connects in one step.

Gotcha: Agent forwarding has its own risk — a root user on the bastion could use your forwarded agent while you are connected. Only forward to hosts you trust, and prefer ProxyJump (which tunnels) over plain agent forwarding when you can.

The modern alternative: SSM Session Manager

AWS Systems Manager Session Manager opens a shell on a private instance through the AWS API — no bastion, no public IP, no open port 22, and no SSH keys. Every session is logged to AWS CloudTrail and can be streamed to Amazon S3 or CloudWatch Logs for a full audit trail. Access is controlled by IAM (Identity and Access Management) policies instead of key files.

Requirements: the instance runs the SSM Agent (preinstalled on Amazon Linux and recent Ubuntu/Windows AMIs), has an IAM instance profile with the AmazonSSMManagedInstanceCore policy, and can reach the SSM service — either through a NAT gateway or, better, through private VPC endpoints so traffic never leaves AWS.

Starting a session is one command:

aws ssm start-session --target i-0a1b2c3d4e5f

Output:

Starting session with SessionId: jdoe-0a1b2c3d4e5f
sh-5.2$

You are now on the private instance with zero inbound ports open. You can even tunnel SSH or forward a port over the same channel:

aws ssm start-session --target i-0a1b2c3d4e5f \
  --document-name AWS-StartPortForwardingSession \
  --parameters '{"portNumber":["80"],"localPortNumber":["8080"]}'

Cost note: Session Manager itself is free; you pay only for the instance and any VPC endpoints (interface endpoints cost about $0.01 per hour each plus data processing). A bastion, by contrast, runs 24/7 — a t3.micro is roughly $7.50 per month even when nobody uses it.

Best practices

  • Prefer SSM Session Manager for new systems; reach for a bastion only when a tool truly needs raw SSH or SCP.
  • If you must run a bastion, allow port 22 only from specific source IPs — never 0.0.0.0/0 — and keep the instance auto-patched.
  • Use SSH agent forwarding or ProxyJump; never copy private keys onto the bastion.
  • Lock the private instances’ security group to accept SSH only from the bastion’s security group, not from anywhere.
  • Turn on VPC Flow Logs and CloudTrail so every connection and session is recorded.
  • Stop or terminate the bastion when it is not needed to shrink both cost and attack surface.
  • Use VPC endpoints for SSM so management traffic stays inside AWS and you can drop the NAT gateway dependency.
Last updated June 15, 2026
Was this helpful?