---
title: "How To Setup Bastion Host on AWS using AWS CLI"
lang: "en"
author: "Mohammad Abu Mattar"
canonical: https://mkabumattar.com/post/how-to-setup-bastion-host-on-aws-using-aws-cli
---

![Blog post image for How To Setup Bastion Host on AWS using AWS CLI - In this post, we will learn the best practices of setting up a Bastion Host on AWS using the AWS CLI for secure and remote access to EC2 instances within a Virtual Private Cloud (VPC). We will guide you through creating a VPC, subnets, internet gateway, and configuring the Bastion Host with the appropriate permissions. This post is intended for those who are familiar with AWS and have some basic knowledge of networking and SSH.](/_astro/hero.DGhoBA0r_Z2k3tdd.webp)

[Home](/)›[Blog](/blog)›[All Categories](/blog/categories)›[AWS](/blog/categories/aws)

Blog

[Prev in AWSHow to Run an Apache Web Server Using Docker on an AWS EC2 Instance](/blog/post/how-to-run-an-apache-web-server-using-docker-on-an-aws-ec2-instance)[Next in AWSHow To Setup Bastion Host on AWS using CloudFormation Template](/blog/post/how-to-setup-bastion-host-on-aws-using-cloudformation-template)

[AWS](/blog/categories/aws)[AWS CLI](/blog/categories/aws-cli)[Security](/blog/categories/security)[Networking](/blog/categories/networking)[DevOps](/blog/categories/devops)

# How To Setup Bastion Host on AWS using AWS CLI

[Mohammad Abu Mattar](/authors/mohammad-abu-mattar)Published: 09 Jan 202315 Mins read22 Mins listen

[Markdown for AI(opens in a new tab)](/post/how-to-setup-bastion-host-on-aws-using-aws-cli/index.md "Open the plain-Markdown version of this page, for pasting into an AI tool")

TL;DR

In this post, we will learn the best practices of setting up a Bastion Host on AWS using the AWS CLI for secure and remote access to EC2 instances within a Virtual Private Cloud (VPC). We will guide you through creating a VPC, subnets, internet gateway, and configuring the Bastion Host with the appropriate permissions. This post is intended for those who are familiar with AWS and have some basic knowledge of networking and SSH.

Series

[AWS Bastion Host Setup](/series/aws-bastion-host-setup)1/2

[NextHow To Setup Bastion Host on AWS using CloudFormation Template](/blog/post/how-to-setup-bastion-host-on-aws-using-cloudformation-template)

All posts in this series (2)

Blog2

1.  [How To Setup Bastion Host on AWS using AWS CLIYou are here](/blog/post/how-to-setup-bastion-host-on-aws-using-aws-cli)
2.  [How To Setup Bastion Host on AWS using CloudFormation Template](/blog/post/how-to-setup-bastion-host-on-aws-using-cloudformation-template)

### How To Setup Bastion Host on AWS using AWS CLI

Contents

[Introduction](#introduction)[Prerequisites](#prerequisites)[Create VPC](#create-vpc)[Step 1: Create VPC](#step-1-create-vpc)[Step 2: Modify your VPC and enable DNS hostname and DNS support](#step-2-modify-your-vpc-and-enable-dns-hostname-and-dns-support)[Step 3: Create a Public and a Private subnet](#step-3-create-a-public-and-a-private-subnet)[Step 4: Enable Auto-assign Public IP on the Public Subnet](#step-4-enable-auto-assign-public-ip-on-the-public-subnet)[Step 5: Create an Internet Gateway](#step-5-create-an-internet-gateway)[Step 6: Create an Elastic IP](#step-6-create-an-elastic-ip)[Step 7: Create a NAT Gateway](#step-7-create-a-nat-gateway)[Step 8: Create a Public and a Private Route Table](#step-8-create-a-public-and-a-private-route-table)[Step 9: Create a Route in the Public Route Table for Internet Gateway](#step-9-create-a-route-in-the-public-route-table-for-internet-gateway)[Step 10: Create a Route in the Private Route Table for NAT Gateway](#step-10-create-a-route-in-the-private-route-table-for-nat-gateway)[Step 11: Associate the Subnets with the Route Tables](#step-11-associate-the-subnets-with-the-route-tables)[Step 12: Create Security Groups](#step-12-create-security-groups)[Create two EC2 instances](#create-two-ec2-instances)[Step 1: Create a Key Pair](#step-1-create-a-key-pair)[Step 2: Get the latest AMI ID for Amazon Linux 2](#step-2-get-the-latest-ami-id-for-amazon-linux-2)[Step 3: Create a Bastion Host](#step-3-create-a-bastion-host)[Step 4: Create a Private Host](#step-4-create-a-private-host)[Connect to the private host](#connect-to-the-private-host)[Step 1: Get the Public IP Address of the Bastion Host](#step-1-get-the-public-ip-address-of-the-bastion-host)[Step 2: Connect to the Bastion Host](#step-2-connect-to-the-bastion-host)[Step 3: Get the Private IP Address of the Private Host](#step-3-get-the-private-ip-address-of-the-private-host)[Step 4: Connect to the Private Host](#step-4-connect-to-the-private-host)[Step 5: Check the Internet Connectivity](#step-5-check-the-internet-connectivity)[Conclusion](#conclusion)[Cleanup](#cleanup)[Delete the EC2 Instances](#delete-the-ec2-instances)[Detach and Delete Internet Gateway](#detach-and-delete-internet-gateway)[Delete Route Tables](#delete-route-tables)[Delete NAT Gateway](#delete-nat-gateway)[Delete Elastic IP](#delete-elastic-ip)[Delete Subnets](#delete-subnets)[Delete Security Groups](#delete-security-groups)[Delete VPC](#delete-vpc)[Delete Key Pair](#delete-key-pair)[References](#references)

## [Introduction](#introduction)

Security is the top priority for any infrastructure and application, and that’s why a Bastion host is a must-have in your infrastructure if you want to secure your remote connections. A Bastion host is a special-purpose computer on a network specifically designed and configured to withstand attacks. In this post, we will show you how to set up a Bastion host on AWS using the AWS CLI. We will create a Virtual Private Cloud (VPC) and subnets, create an Internet Gateway and configure the Bastion host with the appropriate permissions to access our EC2 instances. By the end of this post, you will have a secure and easy way to remotely access your EC2 instances.

## [Prerequisites](#prerequisites)

Before starting, make sure that you have the following:

-   AWS CLI installed and configured on your local machine. You can follow the instructions on [Installing the AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html) to install and configure it.
-   An IAM user with the following permissions:
    -   AmazonVPCFullAccess
    -   AmazonEC2FullAccess
-   Basic knowledge of networking and SSH

To create an IAM user, follow the instructions on [Creating an IAM User](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_users_create.html)

You will also need to have the AWS CLI configured with your access keys for the IAM user you created above. This can be done by running `aws configure` in the command line and providing your access key and secret key.

## [Create VPC](#create-vpc)

In this section, we will create a Virtual Private Cloud (VPC) and its resources such as subnets, internet gateways, etc. This VPC will be the foundation for our Bastion Host, providing a secure and isolated network environment.

### [Step 1: Create VPC](#step-1-create-vpc)

First, we need to create a VPC, run the following command:

Terminal window

```
1AWS_VPC=$(aws ec2 create-vpc \2  --cidr-block 10.0.0.0/16 \3  --query 'Vpc.VpcId' \4  --output text)5
6aws ec2 create-tags \7  --resources $AWS_VPC \8  --tags Key=Name,Value=vpc
```

The above command will create a VPC with the CIDR block of `10.0.0.0/16` and create a name tag for it. The VpcId of the created VPC will be stored in the variable `$AWS_VPC` for future reference.

### [Step 2: Modify your VPC and enable DNS hostname and DNS support](#step-2-modify-your-vpc-and-enable-dns-hostname-and-dns-support)

In this step, we will enable both the DNS hostname and DNS support on our VPC. Enabling these features will allow our instances to resolve DNS hostnames and domain names. By default, these features are disabled when you create a VPC.

Terminal window

```
1aws ec2 modify-vpc-attribute \2  --vpc-id $AWS_VPC \3  --enable-dns-hostnames "{\"Value\":true}"4
5aws ec2 modify-vpc-attribute \6  --vpc-id $AWS_VPC \7  --enable-dns-support "{\"Value\":true}"
```

The above commands will enable the DNS hostname and DNS support for our VPC. `--vpc-id` `$AWS_VPC` this flag tells the command on which vpc you want to enable these features, where the `$AWS_VPC` is a variable that we set in the previous step when we created the VPC.

### [Step 3: Create a Public and a Private subnet](#step-3-create-a-public-and-a-private-subnet)

In this step, we will create two subnets: one public and one private. A public subnet is a subnet that’s connected to the internet through an internet gateway, whereas a private subnet is a subnet that’s isolated from the internet and can only access the internet through a NAT gateway or VPN connection. In this case, we will connect our Bastion Host to the public subnet and the EC2 instances that we want to access remotely to the private subnet.

Terminal window

```
1AWS_PUBLIC_SUBNET=$(aws ec2 create-subnet \2  --vpc-id $AWS_VPC \3  --cidr-block 10.0.0.0/24 \4  --query 'Subnet.SubnetId' \5  --output text)6
7AWS_PRIVATE_SUBNET=$(aws ec2 create-subnet \8  --vpc-id $AWS_VPC \9  --cidr-block 10.0.16.0/24 \10  --query 'Subnet.SubnetId' \11  --output text)12
13aws ec2 create-tags \14  --resources $AWS_PUBLIC_SUBNET \15  --tags Key=Name,Value=public-subnet16
17aws ec2 create-tags \18  --resources $AWS_PRIVATE_SUBNET \19  --tags Key=Name,Value=private-subnet
```

The above command will create two subnets, one with a CIDR block of `10.0.0.0/24` (public) and one with a CIDR block of `10.0.16.0/24` (private) and will associate them with the `$AWS_VPC` VPC we created earlier. Also, it will add names to the subnets for better organization and management. The subnet IDs are stored in the variables `$AWS_PUBLIC_SUBNET` and `$AWS_PRIVATE_SUBNET` for future reference.

### [Step 4: Enable Auto-assign Public IP on the Public Subnet](#step-4-enable-auto-assign-public-ip-on-the-public-subnet)

In this step, we will enable Auto-assign Public IP on the public subnet, which will allow instances launched in this subnet to automatically receive a public IP address. This is necessary for instances that need direct internet access.

Terminal window

```
1aws ec2 modify-subnet-attribute \2  --subnet-id $AWS_PUBLIC_SUBNET \3  --map-public-ip-on-launch
```

The above command will enable the Auto-assign Public IP feature for the public subnet identified by the `$AWS_PUBLIC_SUBNET` variable. This feature tells the AWS to automatically assign a public IP to the instances launched in this subnet.

You can also assign public IP addresses to instances by using Elastic IP addresses, a feature that lets you allocate an IP address to your AWS account and then associate it with an instance. Elastic IP addresses can be moved between instances or released when no longer needed, avoiding the extra charges that come with an automatically assigned public IP address.

### [Step 5: Create an Internet Gateway](#step-5-create-an-internet-gateway)

In this step, we will create an Internet Gateway and associate it with our VPC. An Internet Gateway is a VPC component that allows communication between instances in our VPC and the internet. This is a necessary step for our public subnet instances to have internet access.

Terminal window

```
1AWS_INTERNET_GATEWAY=$(aws ec2 create-internet-gateway \2  --query 'InternetGateway.InternetGatewayId' \3  --output text)4
5aws ec2 create-tags \6  --resources $AWS_INTERNET_GATEWAY \7  --tags Key=Name,Value=internet-gateway8
9aws ec2 attach-internet-gateway \10  --vpc-id $AWS_VPC \11  --internet-gateway-id $AWS_INTERNET_GATEWAY
```

The above command will create an Internet Gateway, create a name tag for it and attach it to the VPC identified by the `$AWS_VPC` variable. The Internet Gateway ID is stored in the variable `$AWS_INTERNET_GATEWAY` for future reference.

Internet Gateways are stateful: if a request initiated from your VPC is sent to an Internet Gateway, the response routes back to the source. A Virtual Private Gateway, by contrast, is stateless. It routes traffic but does not hold connection state. Choose the gateway type that matches your use case.

### [Step 6: Create an Elastic IP](#step-6-create-an-elastic-ip)

In this step, we will create an Elastic IP, which is a static public IPv4 address that can be allocated to your AWS account and then associated with an instance. Having an Elastic IP address allows you to mask the failure of an instance by rapidly remapping the address to another instance. This is useful when instances fail or if you want to change instances while keeping the same IP address.

Terminal window

```
1AWS_ELASTIC_IP=$(aws ec2 allocate-address \2  --domain vpc \3  --query 'AllocationId' \4  --output text)5
6aws ec2 create-tags \7  --resources $AWS_ELASTIC_IP \8  --tags Key=Name,Value=elastic-ip
```

The above command will allocate a new Elastic IP for your AWS account, and create a name tag for it. The Elastic IP address is stored in the variable `$AWS_ELASTIC_IP` for future reference.

Elastic IP addresses are charged for hourly usage when not associated with a running instance, or when associated with a stopped instance or an unattached network interface. If you’re not using it, release it to avoid charges.

### [Step 7: Create a NAT Gateway](#step-7-create-a-nat-gateway)

In this step, we will create a NAT Gateway and associate it with our VPC. A NAT Gateway allows instances in a private subnet to access the internet without exposing their private IP address. It is a highly available, managed service that allows outbound internet traffic from instances in a private subnet in your virtual private cloud (VPC). This is a necessary step for our private subnet instances to have internet access.

Terminal window

```
1AWS_NAT_GATEWAY=$(aws ec2 create-nat-gateway \2  --subnet-id $AWS_PUBLIC_SUBNET \3  --allocation-id $AWS_ELASTIC_IP \4  --query 'NatGateway.NatGatewayId' \5  --output text)6
7aws ec2 create-tags \8  --resources $AWS_NAT_GATEWAY \9  --tags Key=Name,Value=nat-gateway
```

The above command will create a NAT Gateway, associated it with the public subnet identified by the `$AWS_PUBLIC_SUBNET` variable, and using the Elastic IP that we allocated previously identified by `$AWS_ELASTIC_IP`, also, it will create a name tag for it. The NAT Gateway ID is stored in the variable `$AWS_NAT_GATEWAY` for future reference.

The NAT Gateway requires a pre-allocated Elastic IP to be created, and that Elastic IP is consumed by the NAT Gateway and unavailable for other uses. The NAT Gateway is also a managed service, so AWS handles its maintenance and availability.

### [Step 8: Create a Public and a Private Route Table](#step-8-create-a-public-and-a-private-route-table)

In this step, we will create two route tables: one public and one private. A route table contains a set of rules, called routes, that are used to determine where network traffic is directed. Separate route tables for the public and private subnets keep traffic routed correctly.

Terminal window

```
1AWS_PUBLIC_ROUTE_TABLE=$(aws ec2 create-route-table \2  --vpc-id $AWS_VPC \3  --query 'RouteTable.RouteTableId' \4  --output text)5
6AWS_PRIVATE_ROUTE_TABLE=$(aws ec2 create-route-table \7  --vpc-id $AWS_VPC \8  --query 'RouteTable.RouteTableId' \9  --output text)10
11aws ec2 create-tags \12  --resources $AWS_PUBLIC_ROUTE_TABLE \13  --tags Key=Name,Value=public-route-table14
15aws ec2 create-tags \16  --resources $AWS_PRIVATE_ROUTE_TABLE \17  --tags Key=Name,Value=private-route-table
```

The above command will create two route tables associated with the VPC identified by the $AWS\_VPC variable and create names for them “public-route-table” and “private-route-table” and will store the route table IDs in the variables `$AWS_PUBLIC_ROUTE_TABLE`and`$AWS_PRIVATE_ROUTE_TABLE` respectively for future reference.

### [Step 9: Create a Route in the Public Route Table for Internet Gateway](#step-9-create-a-route-in-the-public-route-table-for-internet-gateway)

In this step, we will create a route in the public route table that directs all traffic to the Internet Gateway. This is necessary for instances in the public subnet to have internet access.

Terminal window

```
1aws ec2 create-route \2  --route-table-id $AWS_PUBLIC_ROUTE_TABLE \3  --destination-cidr-block 0.0.0.0/0 \4  --gateway-id $AWS_INTERNET_GATEWAY
```

The above command will create a route in the public route table that directs all traffic (destination CIDR block 0.0.0.0/0) to the Internet Gateway identified by the `$AWS_INTERNET_GATEWAY` variable. This will allow instances in the public subnet to access the internet.

When you create a route in a route table, the route propagates to all associated subnets, so make sure you’re creating it in the correct route table.

### [Step 10: Create a Route in the Private Route Table for NAT Gateway](#step-10-create-a-route-in-the-private-route-table-for-nat-gateway)

In this step, we will create a route in the private route table that directs all traffic to the NAT Gateway. This is necessary for instances in the private subnet to have internet access without exposing their private IP addresses.

Terminal window

```
1aws ec2 create-route \2  --route-table-id $AWS_PRIVATE_ROUTE_TABLE \3  --destination-cidr-block 0.0.0.0/0 \4  --nat-gateway-id $AWS_NAT_GATEWAY
```

The above command will create a route in the private route table that directs all traffic (destination CIDR block 0.0.0.0/0) to the NAT Gateway identified by the `$AWS_NAT_GATEWAY` variable. This will allow instances in the private subnet to access the internet without exposing their private IP addresses.

Instances in the private subnet should not be able to initiate direct internet access. This protects them from malicious internet traffic and reduces the risk of accidental data leaks.

### [Step 11: Associate the Subnets with the Route Tables](#step-11-associate-the-subnets-with-the-route-tables)

In this step, we will associate the public and private subnets with the corresponding public and private route tables. This routes traffic correctly.

Terminal window

```
1aws ec2 associate-route-table \2  --route-table-id $AWS_PUBLIC_ROUTE_TABLE \3  --subnet-id $AWS_PUBLIC_SUBNET4
5aws ec2 associate-route-table \6  --route-table-id $AWS_PRIVATE_ROUTE_TABLE \7  --subnet-id $AWS_PRIVATE_SUBNET
```

The above command will associate the public subnet identified by the `$AWS_PUBLIC_SUBNET` variable with the public route table identified by the `$AWS_PUBLIC_ROUTE_TABLE` variable, and the private subnet identified by the `$AWS_PRIVATE_SUBNET` variable with the private route table identified by the `$AWS_PRIVATE_ROUTE_TABLE` variable.

This directs traffic to the correct destination based on the subnet it originates from: traffic from the public subnet goes to the Internet Gateway, and traffic from the private subnet goes to the NAT Gateway. Instances in the public subnet get internet access, and instances in the private subnet get internet access without exposing their private IP addresses.

### [Step 12: Create Security Groups](#step-12-create-security-groups)

In this step, we will create security groups for the bastion host and the instances in the private subnet. Security groups act as a virtual firewall for your instances, controlling inbound and outbound traffic.

Terminal window

```
1AWS_BASTION_SECURITY_GROUP=$(aws ec2 create-security-group \2  --group-name bastion-security-group \3  --description "Security group for the bastion host" \4  --vpc-id $AWS_VPC \5  --query 'GroupId' \6  --output text)7
8AWS_PRIVATE_SECURITY_GROUP=$(aws ec2 create-security-group \9  --group-name private-security-group \10  --description "Security group for the private instances" \11  --vpc-id $AWS_VPC \12  --query 'GroupId' \13  --output text)14
15aws ec2 create-tags \16  --resources $AWS_BASTION_SECURITY_GROUP \17  --tags Key=Name,Value=bastion-security-group18
19aws ec2 create-tags \20  --resources $AWS_PRIVATE_SECURITY_GROUP \21  --tags Key=Name,Value=private-security-group22
23aws ec2 authorize-security-group-ingress \24  --group-id $AWS_BASTION_SECURITY_GROUP \25  --protocol tcp \26  --port 22 \27  --cidr 0.0.0.0/0 \28  --output text29
30aws ec2 authorize-security-group-ingress \31  --group-id $AWS_PRIVATE_SECURITY_GROUP \32  --protocol tcp \33  --port 22 \34  --source-group $AWS_BASTION_SECURITY_GROUP \35  --output text
```

The above command will create two security groups: one for the bastion host, and one for the instances in the private subnet.

The security group for the bastion host is named “bastion-security-group”, and it is associated with the VPC identified by the `$AWS_VPC` variable. It allows inbound traffic on port 22 (SSH) from any IP address (CIDR block 0.0.0.0/0), which is what lets you connect to instances via the bastion host.

The security group for the instances in the private subnet is named “private-security-group”, and it is also associated with the VPC identified by the `$AWS_VPC` variable. It allows inbound SSH traffic on port 22 from the security group for the bastion host only. This way, instances in the private subnet can only be accessed via the bastion host and only from instances that are within the security group for the bastion host.

These security groups help make sure only authorized traffic can reach your instances, and that traffic from your instances to the internet is properly restricted.

## [Create two EC2 instances](#create-two-ec2-instances)

In this step, we will create two EC2 instances: one for the bastion host and one for the private subnet. Before creating these instances, you need to create an ssh key pair that will be used to access the instances via ssh.

### [Step 1: Create a Key Pair](#step-1-create-a-key-pair)

To connect to the instances via SSH, you will need to create a Key Pair that will be used to authenticate the connection. To create a Key Pair, you can use the AWS Management Console, AWS CLI or SDKs.

Terminal window

```
1AWS_KEY_PAIR=aws-key-pair2aws ec2 create-key-pair \3  --key-name $AWS_KEY_PAIR \4  --query 'KeyMaterial' \5  --output text > $AWS_KEY_PAIR.pem6chmod 400 $AWS_KEY_PAIR.pem
```

The above command will create a new key pair named `aws-key-pair` and the private key will be saved in the file `aws-key-pair.pem`. Make sure to keep this file safe and secure, as it allows you to connect to the instances. The `chmod 400` command will restrict the permissions on the key pair file so that it is only readable by the owner.

Also, you may want to consider using environment variables for the key pair name, this way you can easily change the key pair name without having to search and replace it in the script.

### [Step 2: Get the latest AMI ID for Amazon Linux 2](#step-2-get-the-latest-ami-id-for-amazon-linux-2)

Before launching an EC2 instance, we need to know the Amazon Machine Image (AMI) ID for the specific Operating System we want to use, in this case Amazon Linux 2. The AMI ID is used to specify the image for the instance when it’s being created. Instead of hard coding the AMI ID, it’s better to programmatically find the latest available AMI ID for the specific Operating System so the script always uses the latest version of the AMI.

Terminal window

```
1# Get the latest AMI ID for Amazon Linux 22AWS_AMI=$(aws ec2 describe-images \3  --owners 'amazon' \4  --filters 'Name=name,Values=amzn2-ami-hvm-2.0.*' \5  'Name=state,Values=available' \6  --query 'sort_by(Images, &CreationDate)[-1].[ImageId]' \7  --output 'text')
```

The above command will get the latest available Amazon Linux 2 AMI ID based on the filters provided. The `--owners` option specifies that the AMI should be owned by Amazon. The `--filters` option is used to filter the images returned by the describe-images command. The `Name=name,Values=amzn2-ami-hvm-2.0.*` filter will return only the images whose name starts with `amzn2-ami-hvm-2.0`. The `Name=state,Values=available` filter will return only the images that are in the available state.

### [Step 3: Create a Bastion Host](#step-3-create-a-bastion-host)

In this step, we will use the `run-instances` command to launch an EC2 instance for our Bastion host. The Bastion host will be used as a jump server to securely access the instances in the private subnet.

Terminal window

```
1AWS_BASTION_HOST=$(aws ec2 run-instances \2  --image-id $AWS_AMI \3  --count 1 \4  --instance-type t2.micro \5  --key-name $AWS_KEY_PAIR \6  --security-group-ids $AWS_BASTION_SECURITY_GROUP \7  --subnet-id $AWS_PUBLIC_SUBNET \8  --associate-public-ip-address \9  --query 'Instances[0].InstanceId' \10  --output text)11
12aws ec2 create-tags \13  --resources $AWS_BASTION_HOST \14  --tags Key=Name,Value=bastion-host
```

The above command will create a single EC2 instance in the public subnet with the provided AMI ID and Key Pair, will be associated with the security group that we created earlier for the Bastion host, and it will be assigned a public IP address so that it can be accessed over the internet.

The tag specification was added to assign the Name value to the created instances.

Consider specifying the availability zone so the instances are created in one that meets your requirements (for example, one with enough capacity for the instances).

### [Step 4: Create a Private Host](#step-4-create-a-private-host)

In this step, we will use the run-instances command to launch an EC2 instance for our Private host. The private host will be running in a private subnet and it will not have a public IP address.

Terminal window

```
1AWS_PRIVATE_HOST=$(aws ec2 run-instances \2  --image-id $AWS_AMI \3  --count 1 \4  --instance-type t2.micro \5  --key-name $AWS_KEY_PAIR \6  --security-group-ids $AWS_PRIVATE_SECURITY_GROUP \7  --subnet-id $AWS_PRIVATE_SUBNET \8  --query 'Instances[0].InstanceId' \9  --output text)10
11aws ec2 create-tags \12  --resources $AWS_PRIVATE_HOST \13  --tags Key=Name,Value=private-host
```

The above command will create a single EC2 instance in the private subnet with the provided AMI ID and Key Pair, will be associated with the security group that we created earlier for the private host, and it will not be assigned a public IP address.

The bastion host will act as the gateway for reaching the instances in the private subnet over SSH.

Consider specifying the availability zone so the instances are created in one that meets your requirements (for example, one with enough capacity for the instances).

## [Connect to the private host](#connect-to-the-private-host)

In this section, we will show you how to connect to the Private host using the Bastion host as a jump server.

### [Step 1: Get the Public IP Address of the Bastion Host](#step-1-get-the-public-ip-address-of-the-bastion-host)

To connect to the private host, we first need to know the public IP address of the Bastion host. We can use the `describe-instances` command to get the public IP address of the Bastion host.

Terminal window

```
1AWS_BASTION_HOST_PUBLIC_IP=$(aws ec2 describe-instances \2  --instance-ids $AWS_BASTION_HOST \3  --query 'Reservations[0].Instances[0].PublicIpAddress' \4  --output text)
```

The above command will get the public IP address of the Bastion host based on the instance ID.

Once you have the public IP address of the Bastion host, you can use it to establish an SSH connection to the Bastion host.

### [Step 2: Connect to the Bastion Host](#step-2-connect-to-the-bastion-host)

To connect to the Bastion Host, run the following command:

Terminal window

```
1ssh -i $AWS_KEY_PAIR.pem ec2-user@$AWS_BASTION_HOST_PUBLIC_IP
```

The above command will connect to the Bastion host using the key pair that we created earlier, and the public IP address of the Bastion host.

![Connect to the Bastion Host](/assets/blog/0034-how-to-setup-bastion-host-on-aws-using-aws-cli/connect-to-the-bastion-host.png)

### [Step 3: Get the Private IP Address of the Private Host](#step-3-get-the-private-ip-address-of-the-private-host)

To connect to the private host via the Bastion host, we will need to know the private IP address of the private host. We can use the describe-instances command to get the private IP address of the private host.

Terminal window

```
1AWS_PRIVATE_HOST_PRIVATE_IP=$(aws ec2 describe-instances \2  --instance-ids $AWS_PRIVATE_HOST \3  --query 'Reservations[0].Instances[0].PrivateIpAddress' \4  --output text)
```

The above command will get the private IP address of the private host based on the instance ID. The output is text containing the private IP address, which you can use in the next step.

You can also use the Name tag we created earlier to retrieve the private IP address with the `--filters` option, which gets the private IP address of the private host without needing the instance ID.

Terminal window

```
1AWS_PRIVATE_HOST_PRIVATE_IP=$(aws ec2 describe-instances \2  --filters "Name=tag:Name,Values=private-host" \3  --query 'Reservations[0].Instances[0].PrivateIpAddress' \4  --output text)
```

### [Step 4: Connect to the Private Host](#step-4-connect-to-the-private-host)

Once you have the private IP address of the private host, you can use the following command to connect to the private host via the Bastion host:

Terminal window

```
1# add the private key2vi ~/.ssh/private-key.pem3
4# change the permission of the private key5chmod 400 ~/.ssh/private-key.pem6
7# connect to the private host8ssh -i ~/.ssh/private-key.pem ec2-user@$AWS_PRIVATE_HOST_PRIVATE_IP
```

The above command will connect to the private host using the private key that we created earlier, and the private IP address of the private host.

### [Step 5: Check the Internet Connectivity](#step-5-check-the-internet-connectivity)

Now that you are connected to the private host, you can check if the host has internet connectivity by pinging a public IP or URL:

Terminal window

```
1ping -c 4 google.com
```

The above command will send 4 ICMP echo requests to the IP address of Google’s website, and Google will send back 4 ICMP echo replies if the private host can reach the internet. This verifies that the NAT gateway and route tables are configured correctly.

Alternatively, you can also check internet connectivity by using the curl command to download a webpage:

![Internet Connectivity](/assets/blog/0034-how-to-setup-bastion-host-on-aws-using-aws-cli/internet-connectivity.png)

Terminal window

```
1curl -s https://www.mkabumattar.com | head -n 10
```

This will download the website’s source code and will return it to the terminal, and check if the response is received from the website. If the private host has internet connectivity, it will show the webpage’s source code.

If you run these commands from the bastion host, you might not face the same restrictions as the private host. In that case, run the commands from the private host instead, or test against a site that’s specifically blocked for your private network.

![Internet Connectivity](/assets/blog/0034-how-to-setup-bastion-host-on-aws-using-aws-cli/internet-connectivity-2.png)

## [Conclusion](#conclusion)

In this tutorial, we walked through the process of creating a Bastion host on AWS using the AWS CLI. We created a VPC, subnets, an Internet gateway, a NAT gateway, route tables, security groups, and two EC2 instances. We then connected to the private host via the Bastion host and verified internet connectivity on the private host.

This is just a basic setup. You might want to strengthen security with an authentication method other than the key pair, such as IAM roles and SSM Session Manager, and add extra logging, auditing, and monitoring.

Thanks for reading this tutorial. If you have any questions or feedback, leave a comment below.

## [Cleanup](#cleanup)

When you are finished using the resources created in this tutorial, you should clean them up to avoid incurring unnecessary charges.

You can use the AWS Management Console, or the AWS CLI to delete the resources.

Note

Be careful when running the following commands, as they will delete the resources created in this tutorial

### [Delete the EC2 Instances](#delete-the-ec2-instances)

Terminal window

```
1aws ec2 terminate-instances --instance-ids $AWS_BASTION_HOST $AWS_PRIVATE_HOST
```

This command will delete the two EC2 instances created in this tutorial.

### [Detach and Delete Internet Gateway](#detach-and-delete-internet-gateway)

Terminal window

```
1aws ec2 detach-internet-gateway --internet-gateway-id $AWS_INTERNET_GATEWAY --vpc-id $AWS_VPC2aws ec2 delete-internet-gateway --internet-gateway-id $AWS_INTERNET_GATEWAY
```

This command will detach and delete the Internet gateway created in this tutorial.

### [Delete Route Tables](#delete-route-tables)

Terminal window

```
1aws ec2 delete-route-table --route-table-id $AWS_PUBLIC_ROUTE_TABLE2aws ec2 delete-route-table --route-table-id $AWS_PRIVATE_ROUTE_TABLE
```

This command will delete the route tables created in this tutorial.

### [Delete NAT Gateway](#delete-nat-gateway)

Terminal window

```
1# Wait for NAT Gateway to be deleted if it's in 'deleting' state2aws ec2 wait nat-gateway-deleted --nat-gateway-ids $AWS_NAT_GATEWAY3aws ec2 delete-nat-gateway --nat-gateway-id $AWS_NAT_GATEWAY
```

This command will delete the NAT gateway created in this tutorial.

### [Delete Elastic IP](#delete-elastic-ip)

Terminal window

```
1# Ensure $AWS_ELASTIC_IP holds the AllocationId for release-address2# If $AWS_ELASTIC_IP was set to the PublicIp string, you'd need the AllocationId3# Assuming $AWS_ELASTIC_IP was set using:4# AWS_ELASTIC_IP_ALLOC_ID=$(aws ec2 allocate-address --domain vpc --query 'AllocationId' --output text)5# Then use:6# aws ec2 release-address --allocation-id $AWS_ELASTIC_IP_ALLOC_ID7# If $AWS_ELASTIC_IP was set to the PublicIp string, this command might not work as expected.8# For the script in the article, $AWS_ELASTIC_IP stores AllocationId, so the original command is fine.9aws ec2 release-address --allocation-id $AWS_ELASTIC_IP
```

### [Delete Subnets](#delete-subnets)

Terminal window

```
1aws ec2 delete-subnet --subnet-id $AWS_PUBLIC_SUBNET2aws ec2 delete-subnet --subnet-id $AWS_PRIVATE_SUBNET
```

This command will delete the subnets created in this tutorial.

### [Delete Security Groups](#delete-security-groups)

Terminal window

```
1aws ec2 delete-security-group --group-id $AWS_BASTION_SECURITY_GROUP2aws ec2 delete-security-group --group-id $AWS_PRIVATE_SECURITY_GROUP
```

This command will delete the security groups created in this tutorial.

### [Delete VPC](#delete-vpc)

Terminal window

```
1aws ec2 delete-vpc --vpc-id $AWS_VPC
```

### [Delete Key Pair](#delete-key-pair)

Terminal window

```
1aws ec2 delete-key-pair --key-name $AWS_KEY_PAIR2rm -f $AWS_KEY_PAIR.pem
```

This command will delete the key pair created in this tutorial and also the key file from the local system.

Some resources take a while to fully delete, and some depend on others, so you may need to run the deletion commands multiple times and in a specific order.

Also, you should check if there are any other resources that were created outside of the scope of this tutorial but are still associated with the VPC, subnet, security groups, and key pairs, and delete them as well.

## [References](#references)

-   [AWS Command Line Interface (CLI) User Guide](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-welcome.html)
-   [AWS Identity and Access Management (IAM) User Guide](https://docs.aws.amazon.com/IAM/latest/UserGuide/introduction.html)
-   [Amazon Virtual Private Cloud (VPC) User Guide](https://docs.aws.amazon.com/vpc/latest/userguide/what-is-amazon-vpc.html)
-   [Amazon EC2 User Guide for Linux Instances](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/concepts.html)
-   [Security Groups for your VPC - Amazon VPC](https://docs.aws.amazon.com/vpc/latest/userguide/VPC_SecurityGroups.html)
-   [NAT Gateways - Amazon VPC](https://docs.aws.amazon.com/vpc/latest/userguide/vpc-nat-gateway.html)
-   [Internet Gateways - Amazon VPC](https://docs.aws.amazon.com/vpc/latest/userguide/VPC_Internet_Gateway.html)
-   [AWS Key Pairs and Amazon EC2 Instances](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-key-pairs.html)
-   [Linux Bastion Hosts on AWS (Quick Start Reference Deployment)](https://aws.amazon.com/quickstart/architecture/linux-bastion/)
-   [What is an Elastic IP address? - Amazon EC2](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/elastic-ip-addresses-eip.html)
-   [Connect to your Linux instance using SSH - Amazon EC2](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/AccessingInstancesLinux.html)
-   [AWS CLI Command Reference - ec2 create-vpc](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/ec2/create-vpc.html)
-   [AWS CLI Command Reference - ec2 run-instances](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/ec2/run-instances.html)
-   [AWS Well-Architected Framework - Security Pillar](https://docs.aws.amazon.com/wellarchitected/latest/security-pillar/welcome.html)

Was this useful?

## Tags

[#AWS Bastion Host](/blog/tags/aws-bastion-host)[#AWS CLI](/blog/tags/aws-cli)[#VPC Setup](/blog/tags/vpc-setup)[#EC2 Instance](/blog/tags/ec2-instance)[#AWS Security Groups](/blog/tags/aws-security-groups)[#NAT Gateway](/blog/tags/nat-gateway)[#Internet Gateway](/blog/tags/internet-gateway)[#Secure Remote Access](/blog/tags/secure-remote-access)[#IAM](/blog/tags/iam)[#SSH](/blog/tags/ssh)

## Share

[Facebook](https://facebook.com/sharer/sharer.php?u=https%3A%2F%2Fmkabumattar.com%2Fblog%2Fpost%2Fhow-to-setup-bastion-host-on-aws-using-aws-cli "Share on Facebook")[Twitter](https://twitter.com/intent/tweet/?text=How%20To%20Setup%20Bastion%20Host%20on%20AWS%20using%20AWS%20CLI&url=https%3A%2F%2Fmkabumattar.com%2Fblog%2Fpost%2Fhow-to-setup-bastion-host-on-aws-using-aws-cli "Share on Twitter")[LinkedIn](https://www.linkedin.com/shareArticle?mini=true&url=https%3A%2F%2Fmkabumattar.com%2Fblog%2Fpost%2Fhow-to-setup-bastion-host-on-aws-using-aws-cli&title=How%20To%20Setup%20Bastion%20Host%20on%20AWS%20using%20AWS%20CLI&summary=In%20this%20post%2C%20we%20will%20learn%20the%20best%20practices%20of%20setting%20up%20a%20Bastion%20Host%20on%20AWS%20using%20the%20AWS%20CLI%20for%20secure%20and%20remote%20access%20to%20EC2%20instances%20within%20a%20Virtual%20Private%20Cloud%20\(VPC\).%20We%20will%20guide%20you%20through%20creating%20a%20VPC%2C%20subnets%2C%20internet%20gateway%2C%20and%20configuring%20the%20Bastion%20Host%20with%20the%20appropriate%20permissions.%20This%20post%20is%20intended%20for%20those%20who%20are%20familiar%20with%20AWS%20and%20have%20some%20basic%20knowledge%20of%20networking%20and%20SSH.&source=https://mkabumattar.com "Share on LinkedIn")[WhatsApp](https://wa.me/?text=How%20To%20Setup%20Bastion%20Host%20on%20AWS%20using%20AWS%20CLI%20https%3A%2F%2Fmkabumattar.com%2Fblog%2Fpost%2Fhow-to-setup-bastion-host-on-aws-using-aws-cli "Share on WhatsApp")[Telegram](https://t.me/share/url?url=https%3A%2F%2Fmkabumattar.com%2Fblog%2Fpost%2Fhow-to-setup-bastion-host-on-aws-using-aws-cli&text=How%20To%20Setup%20Bastion%20Host%20on%20AWS%20using%20AWS%20CLI "Share on Telegram")[Reddit](https://www.reddit.com/submit?url=https%3A%2F%2Fmkabumattar.com%2Fblog%2Fpost%2Fhow-to-setup-bastion-host-on-aws-using-aws-cli&title=How%20To%20Setup%20Bastion%20Host%20on%20AWS%20using%20AWS%20CLI "Share on Reddit")[Hacker News](http://news.ycombinator.com/submitlink?u=https%3A%2F%2Fmkabumattar.com%2Fblog%2Fpost%2Fhow-to-setup-bastion-host-on-aws-using-aws-cli&t=How%20To%20Setup%20Bastion%20Host%20on%20AWS%20using%20AWS%20CLI "Share on Hacker News")[Pinterest](https://pinterest.com/pin/create/button/?url=https%3A%2F%2Fmkabumattar.com%2Fblog%2Fpost%2Fhow-to-setup-bastion-host-on-aws-using-aws-cli&media=&description=In%20this%20post%2C%20we%20will%20learn%20the%20best%20practices%20of%20setting%20up%20a%20Bastion%20Host%20on%20AWS%20using%20the%20AWS%20CLI%20for%20secure%20and%20remote%20access%20to%20EC2%20instances%20within%20a%20Virtual%20Private%20Cloud%20\(VPC\).%20We%20will%20guide%20you%20through%20creating%20a%20VPC%2C%20subnets%2C%20internet%20gateway%2C%20and%20configuring%20the%20Bastion%20Host%20with%20the%20appropriate%20permissions.%20This%20post%20is%20intended%20for%20those%20who%20are%20familiar%20with%20AWS%20and%20have%20some%20basic%20knowledge%20of%20networking%20and%20SSH. "Share on Pinterest")[Email](<mailto:?subject=How%20To%20Setup%20Bastion%20Host%20on%20AWS%20using%20AWS%20CLI&body=Check out this article: https%3A%2F%2Fmkabumattar.com%2Fblog%2Fpost%2Fhow-to-setup-bastion-host-on-aws-using-aws-cli>)

## Comments

## You might also enjoy

More posts on similar topics

[![How To Setup Bastion Host on AWS using CloudFormation Template](/_astro/hero.DX_FnUff_Z21DeWl.webp)](/blog/post/how-to-setup-bastion-host-on-aws-using-cloudformation-template)

## [How To Setup Bastion Host on AWS using CloudFormation Template](/blog/post/how-to-setup-bastion-host-on-aws-using-cloudformation-template)

-   [Mohammad Abu Mattar](/authors/mohammad-abu-mattar)
-   [AWS](/blog/categories/aws)
-   [CloudFormation](/blog/categories/cloudformation)
-   [Security](/blog/categories/security)
-   [Networking](/blog/categories/networking)
-   [DevOps](/blog/categories/devops)

Introduction In the previous post, How To Setup Bastion Host on AWS using AWS CLI, we set up a Bastion host with the AWS CLI. Here we w

[#AWS Bastion Host](/blog/tags/aws-bastion-host)[#CloudFormation Template](/blog/tags/cloudformation-template)[#VPC Setup](/blog/tags/vpc-setup)+7 tags

[read more](/blog/post/how-to-setup-bastion-host-on-aws-using-cloudformation-template)

[![How to Setup Jenkins on AWS Using CloudFormation](/_astro/hero.r9yMhIoW_Z3Ovj6.webp)](/blog/post/how-to-setup-jenkins-on-aws-using-cloudformation)

## [How to Setup Jenkins on AWS Using CloudFormation](/blog/post/how-to-setup-jenkins-on-aws-using-cloudformation)

-   [Mohammad Abu Mattar](/authors/mohammad-abu-mattar)
-   [AWS](/blog/categories/aws)
-   [Jenkins](/blog/categories/jenkins)
-   [CloudFormation](/blog/categories/cloudformation)
-   [DevOps](/blog/categories/devops)
-   [CI/CD](/blog/categories/cicd)

Introduction In a previous blog post, we set up Jenkins on AWS using the AWS CLI (How to Install Jenkins on AWS EC2 Instance). In this blog post,

[#AWS CloudFormation](/blog/tags/aws-cloudformation)[#Jenkins Setup](/blog/tags/jenkins-setup)[#EC2 Instance](/blog/tags/ec2-instance)+5 tags

[read more](/blog/post/how-to-setup-jenkins-on-aws-using-cloudformation)

[![How To Connect A Two EC2 Instances Database and Files Transfer Using AWS CLI](/_astro/hero.839lq2GB_1XSap0.webp)](/blog/post/how-to-connect-a-two-ec2-instances-database-and-files-transfer-using-aws-cli)

## [How To Connect A Two EC2 Instances Database and Files Transfer Using AWS CLI](/blog/post/how-to-connect-a-two-ec2-instances-database-and-files-transfer-using-aws-cli)

-   [Mohammad Abu Mattar](/authors/mohammad-abu-mattar)
-   [AWS](/blog/categories/aws)
-   [EC2](/blog/categories/ec2)
-   [EBS](/blog/categories/ebs)
-   [EFS](/blog/categories/efs)
-   [Database](/blog/categories/database)
-   [Networking](/blog/categories/networking)

Introduction In this post, I will show you how to share a database and files between two EC2 instances using AWS CLI. I will use AWS CLI to create a VPC, EC2 instances, EBS, EFS, and security grou

[#AWS CLI](/blog/tags/aws-cli)[#EC2 Instance Communication](/blog/tags/ec2-instance-communication)[#EBS Volume](/blog/tags/ebs-volume)+7 tags

[read more](/blog/post/how-to-connect-a-two-ec2-instances-database-and-files-transfer-using-aws-cli)

[![How To Connect A Two EC2 Instances Data Transfer Using AWS CLI Without AWS EFS](/_astro/hero.WG5UXtzN_2uuusz.webp)](/blog/post/how-to-connect-a-two-ec2-instances-data-transfer-using-aws-cli-without-aws-efs)

## [How To Connect A Two EC2 Instances Data Transfer Using AWS CLI Without AWS EFS](/blog/post/how-to-connect-a-two-ec2-instances-data-transfer-using-aws-cli-without-aws-efs)

-   [Mohammad Abu Mattar](/authors/mohammad-abu-mattar)
-   [AWS](/blog/categories/aws)
-   [EC2](/blog/categories/ec2)
-   [S3](/blog/categories/s3)
-   [Data Transfer](/blog/categories/data-transfer)
-   [AWS CLI](/blog/categories/aws-cli)

Introduction In this post, I will show you how to transfer data between two EC2 instances using AWS CLI, without AWS EFS. We will use an AWS S3 bucket as the middleman. We will create an AWS S3 b

[#AWS S3 Sync](/blog/tags/aws-s3-sync)[#EC2 Data Transfer](/blog/tags/ec2-data-transfer)[#AWS CLI Automation](/blog/tags/aws-cli-automation)+5 tags

[read more](/blog/post/how-to-connect-a-two-ec2-instances-data-transfer-using-aws-cli-without-aws-efs)

[![How To Create An AWS EC2 Instance Using AWS CLI](/_astro/hero.Dh2x9Phr_14dc4A.webp)](/blog/post/how-to-create-an-aws-ec2-instance-using-aws-cli)

## [How To Create An AWS EC2 Instance Using AWS CLI](/blog/post/how-to-create-an-aws-ec2-instance-using-aws-cli)

-   [Mohammad Abu Mattar](/authors/mohammad-abu-mattar)
-   [AWS](/blog/categories/aws)
-   [EC2](/blog/categories/ec2)
-   [VPC](/blog/categories/vpc)
-   [AWS CLI](/blog/categories/aws-cli)
-   [WordPress](/blog/categories/wordpress)
-   [Linux](/blog/categories/linux)

Introduction In this tutorial we will create an AWS EC2 instance with the AWS CLI. We will build the network around it first, then launch the instance with a user data script that installs the Apa

[#AWS CLI EC2](/blog/tags/aws-cli-ec2)[#AWS CLI VPC](/blog/tags/aws-cli-vpc)[#VPC Configuration](/blog/tags/vpc-configuration)+8 tags

[read more](/blog/post/how-to-create-an-aws-ec2-instance-using-aws-cli)

[![How to Deploy a Spring Boot Application to AWS CloudFormation](/_astro/hero.C8XnJRc3_Z1OiL51.webp)](/blog/post/how-to-deploy-a-spring-boot-application-to-aws-cloudformation)

## [How to Deploy a Spring Boot Application to AWS CloudFormation](/blog/post/how-to-deploy-a-spring-boot-application-to-aws-cloudformation)

-   [Mohammad Abu Mattar](/authors/mohammad-abu-mattar)
-   [AWS](/blog/categories/aws)
-   [Spring Boot](/blog/categories/spring-boot)
-   [CloudFormation](/blog/categories/cloudformation)
-   [DevOps](/blog/categories/devops)
-   [Java](/blog/categories/java)

Introduction Deploying a Spring Boot application to the cloud can provide many benefits such as scalability and easy management. AWS CloudFormation is a service that allows for the creation and ma

[#AWS CloudFormation](/blog/tags/aws-cloudformation)[#Spring Boot Deployment](/blog/tags/spring-boot-deployment)[#Java on AWS](/blog/tags/java-on-aws)+5 tags

[read more](/blog/post/how-to-deploy-a-spring-boot-application-to-aws-cloudformation)

6 related posts
