Project: Scalable Web App Using AWS VPC, ALB, and Auto Scaling

In this project, I built a secure and scalable cloud infrastructure using AWS services. I set up a custom VPC with private and public subnets across two Availability Zones, used a bastion host for secure access, launched an Auto Scaling Group, and routed traffic using an Application Load Balancer (ALB).
Everything was set up using the AWS Management Console to clearly understand each step.
Architecture Diagram
Configuration Summary
2 Availability Zones (for high availability)
2 Public Subnets
2 Private Subnets
1 NAT Gateway per AZ
Auto Scaling Group in private subnets
Application Load Balancer (ALB) in public subnets
VPC Creation
From the AWS Console:
Chose “VPC and more” option
AWS automatically created:
VPC
4 subnets (2 public, 2 private)
Route tables
Internet Gateway
2 NAT Gateways
Note: Choosing “VPC only” would require setting up CIDRs, subnets, and gateways manually.
Auto Scaling Group Setup
Step 1: Create Launch Template
Go to EC2 → Launch Templates → Create
Name your template and describe it
Use a recent Ubuntu AMI
Create a new security group
Allow SSH (22) from anywhere
Allow TCP (8000) from anywhere
Select the VPC you just created
Launch the template
Step 2: Create Auto Scaling Group
Go to EC2 → Auto Scaling Groups → Create
Use the launch template
Select the same VPC
Choose the 2 private subnets
Set:
Desired Capacity: 2
Min: 1
Max: 4
Skip load balancer step (we’ll configure ALB separately)
No scaling policy needed
Step 3: Verify EC2 Instances
Check EC2 → Instances and confirm that 2 instances were launched inside the private subnets.
Bastion Host Setup
Private EC2s can't be accessed directly, so I created a bastion (jump) host:
Launched a new EC2 in public subnet
Allowed SSH (22) access
Auto-assigned a public IP
Transferred
.pem
file usingscp
:
$ scp -i my-key.pem my-key.pem ubuntu@<bastion-public-ip>:/home/ubuntu
From the bastion:
$ ssh -i my-key.pem ubuntu@<private-ec2-ip>
Application Deployment
Logged in via bastion
Installed Python dependencies
Ran the application on port 8000 on both instances
Application Load Balancer Setup
Step 1: Create ALB
Go to EC2 → Load Balancers → Create
Type: Application Load Balancer
Name:
cloudscale-alb
Scheme: Internet-facing, IP type: IPv4
Select:
The same VPC
Both public subnets
Use a security group that allows port 80
Step 2: Create Target Group
Type: Instance
Protocol: HTTP
Port: 8000
Register the private EC2s
Attach this target group to the ALB listener (port 80)
Step 3: Update Security Groups
If the app doesn’t load at http://<ALB-DNS>
:
Make sure port 80 is allowed in ALB’s security group
Ensure the target group is using HTTP, not TCP
Challenges I Faced
I mistakenly used TCP 8000 in the target group instead of HTTP 8000, which caused health checks to fail
Forgot to allow port 80 in the security group at first
Final Thoughts
This project helped me learn:
How to set up a production-grade AWS architecture
The role of VPC networking, subnets, NAT, and routing
How Auto Scaling and ALB improve availability and flexibility
Using a bastion host for secure SSH access
Subscribe to my newsletter
Read articles from Titus James directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
