In this project, I designed and deployed a production-level VPC setup on AWS from scratch, focusing on best practices for security, scalability, and high availability.
- ✅ Custom VPC with public and private subnets across two Availability Zones
- ✅ Internet Gateway and dual NAT Gateways for secure outbound internet access
- ✅ Bastion Host setup to securely access private EC2 instances
- ✅ Launch Template & Auto Scaling Group for dynamic instance management
- ✅ Application Load Balancer (ALB) configured to distribute traffic to private instances
- ✅ Sample web application deployed on port 8000 inside private subnets
- ✅ Security Groups and Route Tables configured for controlled traffic flow
To create the VPC, follow these steps:
- Open the AWS Management Console.
- Navigate to the VPC Dashboard.
- Click on Create VPC.
- Provide the following details:
-
Click Create VPC to finalize
-
Wait for the VPC creation process to complete
Let’s take a closer look at the components that were created within the VPC:
Subnets are segments within the VPC that divide it into smaller networks. These are in two Availability Zones (AZs) for redundancy:
prod-example-subnet-public1-ap-south-1a
: Public subnet (accessible from the internet)prod-example-subnet-private1-ap-south-1a
: Private subnet (no direct internet access)
prod-example-subnet-public2-ap-south-1b
: Public subnetprod-example-subnet-private2-ap-south-1b
: Private subnet
Route tables control where traffic goes within the VPC.
prod-example-rtb-public
: Associated with both public subnets; routes include access to the internet via the Internet Gateway.prod-example-rtb-private1-ap-south-1a
: Associated with the private subnet inap-south-1a
.prod-example-rtb-private2-ap-south-1b
: Associated with the private subnet inap-south-1b
.
prod-example-igw
: Internet Gateway – allows public subnets to communicate with the internet.prod-example-nat-public1-ap-south-1a
: NAT Gateway – allows private subnet in AZ 1a to initiate outbound traffic (e.g., software updates) to the internet.prod-example-nat-public2-ap-south-1b
: Same as above for AZ 1b.
A launch template specifies the configuration for EC2 instances in the Auto Scaling Group.
-
Open the AWS Management Console.
-
Navigate to the EC2 Dashboard.
-
Click on Launch Templates in the left-hand menu.
-
Click Create Launch Template.
-
Provide the following details:
- Launch Template Name:
prod-example-launch-template
- AMI ID: Select an appropriate Amazon Machine Image (e.g., Amazon Linux 2).
- Instance Type: Choose an instance type (
t2.micro
). - Key Pair: Select an existing key pair or create a new one.
- Security Groups: Create new securty group and its VPC is selected to the one we created which is
prod-example-vpc
. Also configure inbound security group rules to allow SSH access on port 22 and application access on port 8000.
- Launch Template Name:
-
Click Create Launch Template to save.
An Auto Scaling Group ensures that the desired number of EC2 instances are running.
- Navigate to the Auto Scaling Groups section in the EC2 Dashboard.
- Click Create Auto Scaling Group.
- Provide the following details:
- Configure the scaling policies:
- Desired Capacity: 2
- Minimum Capacity: 1
- Maximum Capacity: 4
- Add health checks and notifications if required.
- Review and click Create Auto Scaling Group.
-
Check the Instances tab to ensure EC2 instances are launched.
-
Verify that instances are distributed across the selected subnets
-
Select one of the EC2 instances and verify its Subnet ID:
- Navigate to the Instances section in the EC2 Dashboard.
- Select an instance from the list.
- In the Details tab, locate the Subnet ID field.
- Confirm that the Subnet ID matches one of the private subnets:
prod-example-subnet-private1-ap-south-1a
prod-example-subnet-private2-ap-south-1b
Since the EC2 instances are in private subnets, they do not have public IP addresses for direct access. To connect to these instances, we need to set up a Bastion Host (or Jump Host) in a public subnet. Follow these steps:
-
Open the AWS Management Console.
-
Navigate to the EC2 Dashboard.
-
Launch a new EC2 instance in one of the public subnets of the VPC created earlier.
-
Configure the instance with the following:
- AMI ID: Select an appropriate Amazon Machine Image (e.g., Amazon Linux 2).
- Instance Type: Choose an instance type (
t2.micro
). - Key Pair: Use the same key pair as the private EC2 instances or create a new one.
- Security Group: Create a security group that allows SSH access (port 22) from your local machine's IP address.
- VPC: Choose our VPC
prod-example-vpc
-
Obtain the public IP address of the Bastion Host:
-
Use the following SSH command to connect to the Bastion Host:
ssh -i <your-key-file>.pem ec2-user@<public-ip-address>
Replace
<your-key-file>.pem
with the name of your private key file and<public-ip-address>
with the public IP address of the Bastion Host. -
To access the private EC2 instances, ensure the
.pem
file (private key) is copied to the Bastion Host. This allows secure SSH connections to the private instances from the Bastion Host.
-
Once connected to the Bastion Host, use SSH to connect to the private EC2 instances:
ssh -i <your-key-file>.pem ec2-user@<private-ip-address>
Replace
<private-ip-address>
with the private IP address of the target EC2 instance.
Note: Ensure that the security group of the private EC2 instances allows SSH access from the Bastion Host's private IP address.
By using a Bastion Host, you can securely access EC2 instances in private subnets without exposing them to the public internet.
To run a sample application on your EC2 instance, follow these steps:
-
Create an HTML file:
- SSH into your private EC2 instance via the Bastion Host.
- Create a file named
index.html
:vi index.html
- Add the following content to the file:
<!DOCTYPE html> <html> <body> <h1>AWS POC production VPC</h1> <p>This project provides a detailed explanation of setting up a production-level VPC.</p> </body> </html>
-
Start a Python HTTP server:
- Run the following command to start a simple HTTP server on port 8000:
python3 -m http.server 8000
- Run the following command to start a simple HTTP server on port 8000:
A Load Balancer distributes incoming traffic across multiple targets, such as EC2 instances, in different Availability Zones.
-
Navigate to the Load Balancers section in the EC2 Dashboard.
-
Click Create Load Balancer.
-
Select Application Load Balancer and click Create.
-
Provide the following details:
- Name:
prod-example-alb
- Scheme: Internet-facing
- IP Address Type: IPv4
- Listeners: Add a listener for HTTP on port 80.
- Availability Zones: Select the VPC created earlier and choose the public subnets.
- Name:
-
Configure the Security Groups:
- select a security group we already created
prod-example-security-group
- select a security group we already created
-
Configure the Target Group:
- Create a new target group:
- Target Type: Instances
- Name:
prod-example-target-group
- Protocol: HTTP
- Port: 8000
- VPC: Select the VPC created in the earlier steps.
- Register the private EC2 instances as targets.
Note: Since the Python application is running on only one EC2 instance, the other instances may appear as unhealthy in the target group.
- Create a new target group:
-
Review and click Create Load Balancer
Our Loadbalancer is ready!!
- Once the Load Balancer is created, note its DNS Name.
- Open a browser and navigate to the Load Balancer's DNS Name.
- You should see the sample application running on the private EC2 instances.
By using a Load Balancer, you ensure high availability and fault tolerance for your application.