Deploy Behind a Bastion

Deploy a workload behind a bastion to harden cloud access. Learn the core concept, step-by-step setup, hands-on walkthrough, and troubleshooting for secure, controlled entry points.

Focus: deploy a workload behind a bastion

Sponsored

You've built and secured individual cloud components — IAM policies, encrypted storage, locked-down VPCs. But there's a gap that attackers love: your application workloads are still reachable from the public internet. That's like locking your front door but leaving the back door wide open. This lesson shows you how to deploy a workload behind a bastion — a hardened, single entry point — so your private resources stay inaccessible to the world while you retain controlled administrative access.

The problem this lesson solves

When you deploy a workload like a web server or a database to a public subnet with a public IP, you expose it to the entire internet. Automated scanners will find it within minutes and probe it for weaknesses. The blast radius of any vulnerability in your workload's software grows from "one compromised container" to "the entire host and everything it can reach." The core problem is that traditional 'jumpbox' access — where you SSH into a public instance first — creates its own challenges: the jumpbox itself becomes a target, and if it's compromised, the attacker inherits its privileges. This lesson gives you a way to deploy a workload behind a bastion so that the workload itself has no public presence, and you have a single, tightly-controlled, auditable path to reach it.

Core concept / mental model

Think of a bastion host as a secure airlock between the internet and your private network. The workload lives in a private subnet, with only a private IP address — it has no public face. To reach it for administration, you must first enter the airlock (the bastion), and from there you can hop to the workload's private IP.

The mental model is a three-layer sandwich:

  1. Internet — where you (the administrator) sit.
  2. Bastion layer — a single, hardened, publicly-accessible host (or a managed service like AWS Systems Manager Session Manager, or GCP IAP tunnel).
  3. Private workload layer — your app, database, or service, living in a private subnet behind a security group that only trusts the bastion's IP.

Key terms you'll use:

  • Bastion host: The public-facing entry point, often a small VM with only SSH/RDP open.
  • Private subnet: A subnet with no internet gateway directly attached; resources inside have only private IPs.
  • Security group: A virtual firewall that controls inbound/outbound traffic; you'll restrict it to allow only traffic from the bastion.
  • Port forwarding / SSH tunneling: Using Secure Shell to create an encrypted tunnel to the private workload.

Pro tip: The bastion is a chokepoint. All admin traffic must pass through it, which means you can enforce MFA, logging, and session monitoring on that single path. You never want to put the workload itself on a public IP.

How it works step by step

  1. Design your VPC with at least two subnets: a public subnet for the bastion, and a private subnet for the workload. The private subnet should have no route to the internet (no Internet Gateway attached).
  2. Deploy the workload in the private subnet with only a private IP. It can still make outbound calls through a NAT gateway if needed, but it cannot be reached from the internet directly.
  3. Create a bastion host in the public subnet. It should have a security group that allows inbound SSH (port 22) only from your corporate CIDR or an approved IP (ideally through a VPN).
  4. Configure the workload's security group to allow inbound traffic (SSH, RDP, or your app port) only from the bastion's private IP (or the bastion's security group ID). This is the critical rule: no public access to the workload.
  5. Access the workload by first SSHing or RDPing into the bastion, and from there SSHing to the workload's private IP. Alternatively, use SSH port forwarding to create a tunnel that lets you access the workload's admin port from your local machine.
  6. Harden the bastion: disable password auth, use key-based auth with MFA, keep the OS patched, and monitor it closely (this is the only public-facing compute in that path).

Hands-on walkthrough

Let's put this into practice with a concrete example. We'll deploy a simple web server behind a bastion on AWS, for illustration. Make sure you have the AWS CLI installed and configured with appropriate credentials.

Prerequisite: You should already have a VPC with public and private subnets, and an SSH key pair. If not, create them in the AWS console first.

Step 1: Create security groups

We'll use the AWS CLI to create a security group for the workload, and one for the bastion.

# Get your VPC ID (replace with your actual VPC ID)
VPC_ID=$(aws ec2 describe-vpcs --query 'Vpcs[0].VpcId' --output text)

# Security group for the bastion — allows SSH from your IP
BASTION_SG=$(aws ec2 create-security-group \
  --group-name bastion-sg \
  --description "Bastion host security group" \
  --vpc-id $VPC_ID \
  --output text)

# Allow SSH from your IP (replace with your public IP)
aws ec2 authorize-security-group-ingress \
  --group-id $BASTION_SG \
  --protocol tcp --port 22 \
  --cidr 203.0.113.10/32

# Security group for the workload — only allows traffic from the bastion
WORKLOAD_SG=$(aws ec2 create-security-group \
  --group-name workload-sg \
  --description "Workload security group" \
  --vpc-id $VPC_ID \
  --output text)

# Allow SSH from the bastion's security group
aws ec2 authorize-security-group-ingress \
  --group-id $WORKLOAD_SG \
  --protocol tcp --port 22 \
  --source-group $BASTION_SG

Step 2: Launch the bastion in a public subnet

# Get the public subnet ID (replace with actual)
PUBLIC_SUBNET_ID=subnet-0abc123def456

aws ec2 run-instances \
  --image-id ami-0abcdef1234567890 \
  --instance-type t3.micro \
  --key-name my-key-pair \
  --security-group-ids $BASTION_SG \
  --subnet-id $PUBLIC_SUBNET_ID \
  --associate-public-ip-address

Step 3: Launch the workload in a private subnet

# Get the private subnet ID (replace with actual)
PRIVATE_SUBNET_ID=subnet-0def456abc789

aws ec2 run-instances \
  --image-id ami-0abcdef1234567890 \
  --instance-type t3.micro \
  --key-name my-key-pair \
  --security-group-ids $WORKLOAD_SG \
  --subnet-id $PRIVATE_SUBNET_ID

Step 4: Connect through the bastion

# Replace with the bastion's public IP and the workload's private IP
ssh -i my-key-pair.pem ec2-user@<bastion-public-ip>
# Now, from inside the bastion:
ssh -i my-key-pair.pem ec2-user@<workload-private-ip>

You are now administrating the workload — and the workload itself was never exposed to the internet.

For newer AWS environments, you could skip the classic bastion entirely and use AWS Systems Manager Session Manager to connect to a workload in a private subnet without any public IP. The same concept applies: a managed bastion service controls access.

Compare options / when to choose what

Option Pros Cons Best for
Classic bastion host (EC2) Full control, low cost, familiar SSH You maintain and secure it; it can be a target Small teams, on-prem-like setups, legacy environments
Managed bastion (AWS SSM, GCP IAP) No public IP, integrated IAM, session logging Requires agent/roles, costs per session Cloud-native teams, zero-trust access, audit-heavy environments
VPN with network ACLs All resources are private, granular network access More complex to set up, not fine-grained per app Enterprises, wide private networks
Jump box + client-side VPN Combines VPN and bastion for extra control Two moving parts Highly regulated environments

Rule of thumb: If you need deep network-level control, go with a classic bastion. If you want to eliminate the public IP entirely and rely on IAM identity, choose a managed service.

Troubleshooting & edge cases

"Connection refused" when connecting to the workload from the bastion

  • Check the workload's security group — it must allow SSH from the bastion's security group, not just any IP.
  • Verify the workload is running and has a private IP in the same VPC.
  • Check route tables — the private subnet must have a route to the NAT gateway if you need outbound internet, but no route to the internet for inbound.

"Host key verification failed"

This happens when the host key on the workload changes (e.g., after re-deployment). On the bastion, remove the stale key:

ssh-keygen -R <workload-private-ip>

"Permission denied (publickey)"

  • Make sure your key pair on the bastion matches the workload's authorized_keys.
  • Check the ~/.ssh/ directory permissions — it must be 700, and authorized_keys should be 600.

"Bastion is being probed"

Even with a bastion, the public IP will attract scanners. You can mitigate this by: - Changing the SSH port (though security-through-obscurity is weak; rely on security groups and MFA instead). - Using a security group that allows only your current IP (CIDR must be updated as your IP changes). - Enabling fail2ban on the bastion to block repeated brute-force attempts.

What you learned & what's next

You now can deploy a workload behind a bastion, understanding why it matters and how to implement it with cloud tooling. You've learned to:

  • Explain the core concept of a bastion host and a private workload.
  • Create security groups that enforce a single access path.
  • Launch resources in private and public subnets and connect via the bastion.
  • Troubleshoot common connection issues.
  • Compare bastion, managed bastion, VPN, and jump-box solutions to choose what fits your scenario.

In the next lesson, you'll learn how to monitor access through your bastion — using audit logs and session tools to detect misuse. That's the natural next step in hardening your cloud environment.

Remember: The bastion is the last line between the internet and your private workloads. Treat it as the highest-risk asset in your network and protect it accordingly.

Practice recap

As a hands-on exercise, create a new VPC with public and private subnets, deploy a small web server in the private subnet with no public IP, and set up a bastion host. Then connect from your local machine using SSH port forwarding to access the web server's admin console via the bastion. This will cement the pattern of protected access that you'll rely on in production.

Common mistakes

  • Putting the workload's security group rule for port 22 open to 0.0.0.0/0, which defeats the bastion's purpose — always restrict to the bastion's SG or IP.
  • Forgetting that the private subnet has no route to the internet, so the workload cannot fetch system packages unless you attach a NAT gateway.
  • Using the same SSH key pair for both the bastion and the workload — if the bastion is compromised, the key is useless for the workload if you use a different pair and forward agent, so use distinct keys or rely on agent forwarding.
  • Leaving the bastion with password-based SSH enabled — this invites brute-force attacks; disable it and use key-based auth plus MFA.

Variations

  1. Use a managed bastion like AWS Systems Manager Session Manager or GCP Identity-Aware Proxy (IAP) to avoid maintaining a VM entirely.
  2. Combine the bastion with a client-side VPN so all admin traffic enters through an encrypted tunnel plus the bastion path.
  3. Employ SSH port forwarding (ssh -L) to tunnel any local application port (e.g., a database GUI) through the bastion to a private workload.

Real-world use cases

  • Administering an internal database in a private subnet of a VPC without giving it a public IP — you connect via the bastion to run migrations or queries.
  • Deploying a microservice that should only be reached by internal services or operators, with no public endpoint, using the bastion for debugging and log retrieval.
  • Managing a legacy application that requires direct administrative access but must stay behind a corporate firewall — the bastion provides a secure, audited entry point.

Key takeaways

  • A bastion host is the only public entry point; the workload itself must have no public IP to reduce attack surface.
  • Security groups are your primary enforcement — restrict all inbound rules for the workload to just the bastion's SG or IP.
  • Private subnets need no internet gateway, but a NAT gateway is required for outbound-patch or package updates.
  • You can choose classic bastion, managed bastion (e.g., SSM), or VPN-based access — pick based on control, auditability, and operational overhead.
  • Always troubleshoot connection issues by validating SG rules, key permissions, and route tables — in that order.
  • A bastion is a critical asset; secure it with MFA, patching, and monitoring to prevent it from becoming a pivot point.

Sponsored

Sponsored

Discussion

Questions, corrections, and tips help everyone reading this page.

0 comments

Add a comment

Shown publicly with your comment.

Be constructive · max 4,000 characters

No comments yet — start the thread.

Related tutorials, quizzes, and articles for this topic.