Understanding Bastion Hosts for Cloud Security
What is a Bastion Host & When Do You Need It?
A Bastion Host is a server placed in a public subnet within your cloud environment that acts as a jump server for accessing resources in internal or private network subnets. Instead of allowing direct SSH or RDP access to internal servers, all access is channeled through the Bastion Host. This helps limit the exposure of internal resources (such as databases, private APIs, or internal-only microservices) and reduces the attack surface.
A Bastion Host ensures that only authorized users can manage these resources by enforcing stricter access control, logging, and monitoring, adding an extra layer of protection.
How Bastion Hosts Enhance Security within Your Cloud Environments
Preventing Direct Access
Without a Bastion Host, users could connect directly to critical infrastructure, like databases and internal services, using SSH or RDP. This increases the attack surface and makes it easier for attackers to gain unauthorized access. The Bastion Host acts as the only point of entry, enforcing access controls and authentication measures before allowing access to any internal network resources.
Reducing Attack Surface
By forcing all remote access through a single, secure point (the Bastion Host), you limit the number of potential access points into your private network. This reduces the attack surface, making it more difficult for attackers to exploit vulnerabilities.
Mitigating Brute Force Attacks
Bastion Hosts can be configured to block IP addresses after a set number of failed login attempts. This prevents attackers from trying to guess passwords via brute force on important systems like SSH or RDP.
Bastion Host vs. Jump Server vs. VPN
The terms Bastion Host, Jump Server, and VPN are sometimes used interchangeably, but they serve different purposes:
- Bastion Host vs. Jump Server: A Bastion Host is a specifically hardened system designed to withstand external attacks with strict security policies and monitoring. A Jump Server, on the other hand, is simply an intermediary server for SSH access without necessarily being hardened or monitored.
- Bastion Host vs. VPN: A VPN allows users to create an encrypted tunnel into your network, exposing more resources than necessary. In contrast, a Bastion Host only allows users to connect to the resources they need, and access is strictly controlled and monitored.
How to Set Up a Bastion Host
Below is a step-by-step guide for setting up a Bastion Host in AWS.
Prerequisites
- AWS CLI installed and configured.
- IAM user is created with AmazonVPCFullAccess and AmazonEC2FullAccess permissions.
- Basic knowledge of networking and SSH.
- AWS CLI configured with the IAM user’s access keys (aws configure).
Assuming the above is true, let's dive into the steps below, and see how Kapstan can help automate much of these mundane, repetitive tasks.
Step 1: Create a VPC
When setting up a network in AWS, creating a Virtual Private Cloud (VPC) is the foundational step. The command below first creates a VPC with a CIDR block of 10.0.0.0/16, which defines the range of IP addresses available within this private network. The --query 'Vpc.VpcId' --output text extracts and stores the generated VPC ID in the AWS_VPC variable for later use. The second command assigns a tag to the VPC, labeling it with Name=vpc, which helps in organizing and identifying resources in AWS. Tagging is crucial for managing resources efficiently, improving visibility, and enabling automation in larger cloud environments.
AWS_VPC=$(aws ec2 create-vpc \
--cidr-block 10.0.0.0/16 \
--query 'Vpc.VpcId' \
--output text)
aws ec2 create-tags \
--resources $AWS_VPC \
--tags Key=Name,Value=vpc
Step 2: Enable DNS Hostnames and Support
Next, we need to modify the attributes of the VPC to enhance it's DNS functionality. The first command enables DNS hostnames, allowing instances launched in the VPC to receive a public DNS name, which is especially useful for public-facing services. The second command enables DNS support, allowing the VPC to resolve domain names using Amazon’s DNS service, which is crucial for internal name resolution and services like AWS PrivateLink.
Enabling these features improves network flexibility, making it easier to manage instances, connect to AWS services, and support hybrid cloud architectures.
Step 3: Create Public and Private Subnets
Creating both public and private subnets in a VPC is generally a best practice for security and efficient resource management in AWS.
- Public Subnets are designed for resources that need direct access to the internet, such as web servers. These subnets are typically associated with an Internet Gateway, allowing instances within them to communicate externally.
- Private Subnets are used for resources that should remain isolated from direct internet access, such as databases or backend services. These subnets often route internet-bound traffic through a NAT Gateway in the public subnet, providing outbound connectivity while keeping them shielded from direct inbound traffic.
This setup enhances security, ensuring critical resources remain protected while allowing necessary internet access for updates and API calls.
AWS_PUBLIC_SUBNET=$(aws ec2 create-subnet \
--vpc-id $AWS_VPC \
--cidr-block 10.0.1.0/24 \
--query 'Subnet.SubnetId' \
--output text)
AWS_PRIVATE_SUBNET=$(aws ec2 create-subnet \
--vpc-id $AWS_VPC \
--cidr-block 10.0.16
Step 4: Disable Auto-assign Public IP for the Public Subnet
Rather than enabling auto-assign for all instances in the public subnet, disable the default setting and manually assign public IPs only for the Bastion Host
aws ec2 modify-subnet-attribute --subnet-id $AWS_PUBLIC_SUBNET --no-map-public-IP-on-launch
Step 5: Create an Internet Gateway
An Internet Gateway (IGW) in AWS is essential for enabling internet access to resources inside a VPC. It serves as a bridge between your VPC and the public internet, allowing instances in public subnets to send and receive traffic.
Internet Gateways serve the purpose of allowing your VPC to connect with the outside internet, unless other networking components are used.
AWS_INTERNET_GATEWAY=$(aws ec2 create-internet-gateway \
--query 'InternetGateway.InternetGatewayId' \
--output text)
aws ec2 attach-internet-gateway \
--vpc-id $AWS_VPC \
--internet-gateway-id $AWS_INTERNET_GATEWAY
Step 6: Create an Elastic IP
An Elastic IP (EIP) in AWS is a static (unchanging) public IP address that you can attach to your cloud resources. Normally, when an AWS instance restarts, its public IP might change. But with an Elastic IP, you get a fixed address that won’t change, ensuring reliable access.
Why Use an Elastic IP?
- Keep a Fixed IP Address: Useful for servers that need a permanent public IP, like a web server or a remote access machine.
- Quick Recovery: If a server crashes, you can move the Elastic IP to a new instance without updating settings.
- Enable Internet Access for NAT Gateways: Private subnet resources (like databases) need an Elastic IP for internet access through a NAT Gateway.
- Better Security Control: You can allow traffic only from specific IPs, improving security.
The second command tags the Elastic IP, making it easier to find and manage inside AWS.
AWS_ELASTIC_IP=$(aws ec2 allocate-address \
--domain vpc \
--query 'AllocationId' \
--output text)
aws ec2 create-tags \
--resources $AWS_ELASTIC_IP \
--tags Key=Name,Value=elastic-ip
Step 7: Create a NAT Gateway
A NAT Gateway allows private resources (such as databases, or internal services) to reach the internet safely without making them publicly accessible.
AWS_NAT_GATEWAY=$(aws ec2 create-nat-gateway \
--subnet-id $AWS_PUBLIC_SUBNET \
--allocation-id $AWS_ELASTIC_IP \
--query 'NatGateway.NatGatewayId' \
--output text)

Step 8: Create Public and Private Route Tables
Create route tables for the public and private subnets:
AWS_PUBLIC_ROUTE_TABLE=$(aws ec2 create-route-table \
--vpc-id $AWS_VPC \
--query 'RouteTable.RouteTableId' \
--output text)
AWS_PRIVATE_ROUTE_TABLE=$(aws ec2

Step 9: Add Routes for the Internet Gateway and NAT Gateway
- Public Route Table: Direct all traffic to the Internet Gateway.
- Private Route Table: Direct traffic to the NAT Gateway for private subnet access.
# Public Route Table: Direct all traffic to the Internet Gateway
aws ec2 create-route --route-table-id $AWS_PUBLIC_ROUTE_TABLE \
--destination-cidr-block 0.0.0.0/0 --gateway-id $AWS_INTERNET_GATEWAY
# Private Route Table: Direct traffic to the NAT Gateway for private subnet access
aws ec2 create-route --route-table-id $AWS_PRIVATE_ROUTE_TABLE \
--destination-cidr-block 0.0.0.0/0 --nat-gateway-id $AWS_NAT_GATEWAY
Step 10: Associate Subnets with Route Tables
# Associate the public subnet with the public route table
aws ec2 associate-route-table --route-table-id $AWS_PUBLIC_ROUTE_TABLE \
--subnet-id $AWS_PUBLIC_SUBNET
# Associate the private subnet with the private route table
aws ec2 associate-route-table --route-table-id $AWS_PRIVATE_ROUTE_TABLE \
--subnet-id $AWS_PRIVATE_SUBNET
Step 11: Create Security Groups
# Bastion Host security group - SSH only from trusted IPs
AWS_BASTION_SECURITY_GROUP=$(aws ec2 create-security-group --group-name bastion-security-group \
--description "Bastion host security group" --vpc-id $AWS_VPC --query 'GroupId' --output text)
aws ec2 authorize-security-group-ingress --group-id $AWS_BASTION_SECURITY_GROUP \
--protocol tcp --port 22 --cidr <trusted_ip_range>/32
aws ec2 create-tags --resources $AWS_BASTION_SECURITY_GROUP \
--tags Key=Name,Value=bastion-security-group
# Private instances security group - Allow access only from Bastion Host
AWS_PRIVATE_SECURITY_GROUP=$(aws ec2 create-security-group --group-name private-security-group \
--description "Private instances security group" --vpc-id $AWS_VPC --query 'GroupId' --output text)
aws ec2 authorize-security-group-ingress --group-id $AWS_PRIVATE_SECURITY_GROUP \
--protocol tcp --port 22 --source-group $AWS_BASTION_SECURITY_GROUP
aws ec2 create-tags --resources $AWS_PRIVATE_SECURITY_GROUP \
--tags Key=Name,Value=private-security-group

Step 12: Create EC2 Instances
- Bastion Host: Launched in the public subnet.
- Private Host: Launched in the private subnet.
Create a key pair for SSH access:
# Define the key pair name
AWS_KEY_PAIR=aws-key-pair
# Create a new key pair and save it to a file
aws ec2 create-key-pair --key-name $AWS_KEY_PAIR \
--query 'KeyMaterial' --output text > $AWS_KEY_PAIR.pem
# Set the correct permissions for the private key
chmod 400 $AWS_KEY_PAIR.pem

Launch both EC2 instances:
# Launch Bastion Host
AWS_BASTION_HOST=$(aws ec2 run-instances --image-id $AWS_AMI --count 1 \
--instance-type t2.micro --key-name $AWS_KEY_PAIR \
--security-group-ids $AWS_BASTION_SECURITY_GROUP \
--subnet-id $AWS_PUBLIC_SUBNET --query 'Instances[0].InstanceId' --output text)
# Associate the Elastic IP created in Step 6 with the Bastion Host EC2 instance
aws ec2 associate-address --instance-id $AWS_BASTION_HOST --allocation-id $BASTION_ELASTIC_IP
# Launch Private Host
AWS_PRIVATE_HOST=$(aws ec2 run-instances --image-id $AWS_AMI --count 1 \
--instance-type t2.micro --key-name $AWS_KEY_PAIR \
--security-group-ids $AWS_PRIVATE_SECURITY_GROUP \
--subnet-id $AWS_PRIVATE_SUBNET --query 'Instances[0].InstanceId' --output text)

Step 13: Connect to Private Host (Securely)
You can access instances using AWS Systems Manager Session Manager for secure, keyless access:
# Initiate a Session to the Bastion Host (no SSH keys required)
aws ssm start-session --target $AWS_BASTION_HOST
# From the Bastion Host, use SSH or session manager to connect to the Private Host
aws ssm start-session --target $AWS_PRIVATE_HOST
# Alternatively, use traditional SSH for Bastion Host access (only if absolutely necessary)
# Get the public IP of the Bastion Host
AWS_BASTION_HOST_PUBLIC_IP=$(aws ec2 describe-instances --instance-ids $AWS_BASTION_HOST \
--query 'Reservations[0].Instances[0].PublicIpAddress' --output text)
# Connect to the Bastion Host via SSH
ssh -i $AWS_KEY_PAIR.pem ec2-user@$AWS_BASTION_HOST_PUBLIC_IP

- From the Bastion Host, connect to the Private Host using its private IP.
ssh -i ~/.ssh/private-key.pem ec2-user@$AWS_PRIVATE_HOST_PRIVATE_IP

Setting Up a Bastion Host on GCP
Here’s a step-by-step guide to setting up a Bastion Host in GCP:
Step 1: Create a Virtual Private Cloud (VPC)
If you don’t already have a VPC, you need to create one.
- Go to the Google Cloud Console: https://console.cloud.google.com/
- Navigate to VPC Network in the left-hand menu.
- Click Create VPC Network.
- Give it a name (e.g., my-vpc).
- Select the desired subnets and IP ranges for your private and public subnets. You will need at least one subnet for the Bastion Host (typically public) and others for private instances.
- Click Create.
Step 2: Create a Bastion Host (VM)
Now that you have a VPC, you can create a Bastion Host (a public VM that will be accessible via SSH).
- Go to Compute Engine and click Create Instance.
- Name the instance (e.g., bastion-host).
- In the Region and Zone, select a zone for your VM.
- Under Machine configuration, select the desired machine type (e.g., e2-micro for lightweight usage).
- In the Boot disk, select a Linux-based OS (e.g., Ubuntu, Debian, CentOS).
- Under Firewall, select Allow HTTP traffic and Allow HTTPS traffic to ensure the Bastion Host is publicly accessible.
- Under Network interface, select the VPC you created earlier and ensure the subnet is public.
- Click Create.
Step 3: Assign a Static External IP to the Bastion Host
To ensure the Bastion Host is accessible from outside your GCP network, you will need to assign it a static external network IP address.
- Go to VPC Network > External IP addresses.
- Find the IP assigned to the Bastion Host instance.
- Click Reserve Static Address and give it a name (e.g., bastion-ip).
- Click Reserve.
Step 4: Configure SSH Access to the Bastion Host
Ensure your SSH key is set up. If you don't already have one, generate it using:
ssh-keygen -t rsa -b 2048 -f ~/.ssh/bastion_key
- Add the public SSH key to your Bastion Host. You can do this either manually by uploading it to the VM or via the Google Cloud Console under the Metadata section of the VM’s SSH Keys tab.
Test SSH access to the Bastion Host using:
ssh -i ~/.ssh/bastion_key USERNAME@<external-ip-of-bastion>
- Replace USERNAME with your username on the VM (often ubuntu or debian), and <external-ip-of-bastion> with the public IP address of your Bastion Host.
Step 5: Configure Private Instances
- Create a private VM inside a private subnet within your VPC.
- Ensure the private instance has no public IP, which is important for security.
- You can now SSH from the Bastion Host into the private VM using the internal IP.
For example, connect to the Bastion Host and then SSH into the private instance:
ssh -i ~/.ssh/bastion_key USERNAME@<private-ip-of-instance>
Best Practices for Managing Bastion Hosts
In this section, we’ll focus on specific security measures that should be implemented to ensure Bastion Hosts remain secure and are effectively managed.
Restrict SSH Access
Limit SSH access to the Bastion Host by allowing only trusted IP addresses. This prevents unauthorized SSH connections and reduces the risk of external attacks.
Implement Least Privilege
Grant users the minimum access necessary for their roles, ensuring sensitive resources like databases and internal APIs remain secure, shell-protected from unnecessary exposure.
Enable MFA
Multi-factor authentication (MFA) is required to access the Bastion Host. This adds an extra layer of security, ensuring that even if credentials are compromised, unauthorized access is still blocked.
Enable Logging and Monitoring
Use tools like CloudTrail (AWS) or Audit Logs (GCP) to track activity on the Bastion Host. This allows you to monitor access and detect potential security incidents early.
Now, managing Bastion Hosts across different cloud environments can be a bit difficult. For developers and administrators, ensuring secure access to critical resources like databases, APIs, and internal services is important, but doing this manually can lead to issues. You end up managing different credentials, ensuring proper security, and controlling access in several places, which can lead to confusion and potential security risks. The process can also slow down development teams, who often need quick, easy access to these resources.
How Kapstan Solves This Problem?
Kapstan simplifies and automates the management of Bastion Hosts, making it easier to control access to cloud resources in a consistent, secure way. Instead of dealing with separate systems for each environment, Kapstan integrates everything, reducing complexity and ensuring that only authorized users can access sensitive data resources.
Centralized Management of Environments
With Kapstan, you can easily manage your environments from a single interface. You can set up different environments like Infra, Production, and Staging, all in one place.

This shows how you can see and manage your environments. You’ll have a clear view of all your environments and manage their access from a single point.
Role-Based Access Control (RBAC)
Kapstan lets you define who can access what through roles. For example, developers who only need to view logs can have restricted access, while system admins who need full control can have more permissions.

This image shows how roles can be easily assigned. For example, by assigning someone the Application Developer role, they get the right level of access, such as the ability to connect to specific Bastion Hosts without needing full admin access.
Secure and Simple Access
Instead of dealing with complex credentials like SSH keys or VPNs, Kapstan automatically manages secure access to your Bastion Hosts. It handles things like multi-factor authentication (MFA) and ensures that the right people have access at the right time. This makes the process more secure and less prone to errors.

While Kapstan provides a simple authentication layer for Bastion Hosts, developers and administrators still need an efficient way to interact with their cloud resources. The Kapstan CLI enables users to securely connect to services, execute operational tasks, and manage access policies - all from the command line.
To get started, install the Kapstan CLI and log in:
kapstan login
kapstan configure

After authentication, Kapstan automatically provisions a secure, short-lived access token. This token ensures that connections are time-bound and adhere to the organization's security policies. The CLI also retrieves environment details and associates the user with their assigned roles, enforcing access controls dynamically.
To check available services in the current environment, use:
kapstan list services

When a connection request is made, Kapstan evaluates the user’s permissions in real-time, ensuring compliance with Role-Based Access Control (RBAC) policies. If access is granted, the CLI initiates a secure tunnel to the requested service. For example, to connect to a database:
kapstan connect service -o="<organization-name>" -e="environment-name" -s="<service-name>"
Kapstan securely proxies connections through its Bastion Host, preventing direct exposure of database endpoints. Acting as an intermediary, it enforces security policies and logs all access requests, eliminating static credentials while maintaining auditability and compliance.
Kapstan CLI supports various cloud services, providing a consistent experience across environments. Whether connecting to a database, managing services, or handling access policies, the CLI streamlines operations without compromising security.
By bringing everything into one platform, Kapstan keeps your cloud resources safe and gives developers easy, secure access. This saves time and improves your cloud security, letting you focus on growing and improving your systems with confidence.
Conclusion
Till now, you should have a clear understanding of how a Bastion Host secures your cloud infrastructure by controlling remote access to important resources like databases and APIs. It reduces the attack surface, prevents unauthorized remote access security down, and mitigates brute-force attacks. By enforcing best practices such as strict access controls, logging, and multi-factor authentication, a Bastion Host ensures secure, controlled access to your cloud environment.
---------------------------------------------------------
Frequently Asked Questions
What is a Bastion Host, and how does it work?
A Bastion Host is a server placed in a public subnet that acts as a bastion host works a secure gateway for accessing private resources in a network. It routes all remote access (e.g., SSH or RDP) through a single, monitored entry point.
Why is a Bastion Host important for infrastructure security?
A Bastion Host is critical for preventing direct access to internal systems, reducing the attack surface by using perimeter access control security, ensuring that only authorized users can access sensitive infrastructure via a controlled, secure gateway.
How can developers implement a Bastion Host effectively?
Developers can implement a Bastion Host by configuring it with the least privilege access, ensuring it is hardened against attacks, and using it as the only point for secure remote access to private resources, with proper logging and monitoring enabled.
What are the best practices for maintaining a Bastion Host?
Best practices for maintaining a Bastion Host include restricting access with IP whitelisting, enforcing multi-factor authentication (MFA), enabling detailed logging and auditing, and regularly updating and patching the bastion host servers to fix security vulnerabilities.