Introduction
In this project, I built a secure, highly available AWS architecture that mirrors real-world production environments.
The goal was to practice network segmentation, private connectivity, high availability, and observability using native AWS services.
By the end of this guide, you will understand how to deploy:
- A bastion-managed environment
- A private, auto-scaled application layer
- Secure VPC-to-VPC communication using a Transit Gateway
- End-to-end logging and DNS routing
Architecture Overview
What We Are Deploying
- Two VPCs with isolated responsibilities
- Transit Gateway for private communication
- Public and private subnets across multiple AZs
- Bastion host for secure access
- Auto Scaling Group behind a Network Load Balancer
- Centralized logging and DNS routing
VPC Design
- VPC-A (Bastion / Management): Secure administrative access
- VPC-B (Application): Highly available, private application layer
Prerequisites
Before starting, ensure you have the following in place:
- AWS account
- IAM user with administrator access
- AWS CLI installed and configured
- EC2 key pair created
- A registered domain managed in Route53
Step 1: Create the VPCs
We start by isolating responsibilities using two VPCs.
Bastion VPC
- CIDR:
192.168.0.0/16 - Name:
bastion-vpc
Application VPC
- CIDR:
172.32.0.0/16 - Name:
app-vpc
This separation ensures administrative access is decoupled from the application environment.
Step 2: Create Subnets
Bastion VPC
| Subnet | CIDR | AZ | Type |
|---|---|---|---|
| Public | 192.168.1.0/24 | us-east-1a | Public |
Application VPC
| Subnet | CIDR | AZ | Type |
|---|---|---|---|
| Public | 172.32.1.0/24 | us-east-1a | Public |
| Private | 172.32.2.0/24 | us-east-1a | Private |
| Private | 172.32.3.0/24 | us-east-1b | Private |
Private subnets across multiple AZs provide high availability.
Step 3: Internet Gateways
Each VPC requires an Internet Gateway for public connectivity.
- Create an IGW for both VPCs
- Attach each IGW to its respective VPC
-
Update public route tables:
0.0.0.0/0 → Internet Gateway
Step 4: NAT Gateway
Private instances still need outbound internet access.
- Allocate an Elastic IP
- Create a NAT Gateway in the public subnet of the Application VPC
-
Update private route tables:
0.0.0.0/0 → NAT Gateway
Step 5: Transit Gateway
To enable private VPC-to-VPC communication, we introduce a Transit Gateway.
- Create a Transit Gateway
-
Attach:
- Bastion VPC
- Application VPC
-
Update route tables in both VPCs:
- Destination: Other VPC CIDR
- Target: Transit Gateway
This eliminates the need for VPC peering and scales better.
Step 6: Security Groups
Bastion Security Group
- Allow SSH (22) from your IP
- Outbound: All traffic
Application Security Group
- Allow SSH (22) from Bastion SG
- Allow HTTP (80) from Load Balancer
- Outbound: All traffic
This enforces least-privilege network access.
Step 7: Bastion Host
- Launch EC2 in the Bastion VPC public subnet
-
Attach:
- Bastion security group
- Elastic IP
Instance type:
t3.micro
This host becomes the only SSH entry point into the environment.
Step 8: Create a Golden AMI
We build a reusable image for consistency and scaling.
Instance Setup
sudo apt update -y
sudo apt install apache2 git awscli amazon-cloudwatch-agent -y
sudo systemctl start apache2
sudo systemctl enable apache2
Installed components:
- Apache
- Git
- AWS CLI
- CloudWatch Agent
- SSM Agent
Stop the instance and create an AMI named golden-ami.
Step 9: CloudWatch Logs and VPC Flow Logs
Observability is critical in production systems.
- Create CloudWatch Log Group:
/vpc/flowlogs - Enable VPC Flow Logs for both VPCs
- Destination: CloudWatch
- Separate log streams per VPC
Step 10: IAM Role for EC2
Create an IAM role with:
- Trusted entity: EC2
-
Policies:
AmazonSSMManagedInstanceCore- Custom S3 read-only policy (restricted bucket access)
This enables secure instance management without SSH keys.
Step 11: S3 Bucket for App Configuration
- Create an S3 bucket
- Enable encryption
- Block public access
- Store application configuration files
Step 12: Launch Configuration
Use the Golden AMI and configure:
- Instance type:
t3.micro - Security group: Application SG
- IAM role attached
User Data
#!/bin/bash
sudo apt install git -y
git clone <repo-url> /var/www/html
systemctl restart apache2
Step 13: Auto Scaling Group
- Min: 2
- Desired: 2
- Max: 4
-
Subnets:
- Private subnet (us-east-1a)
- Private subnet (us-east-1b)
This ensures fault tolerance and scalability.
Step 14: Target Group
- Target type: Instance
- Protocol: TCP
- Port: 80
- Health checks enabled
Step 15: Network Load Balancer
- Internet-facing
- Subnets: Public subnets
- Listener: TCP :80
- Forward traffic to ASG target group
Step 16: Route53 DNS
Create a DNS record:
- Type: CNAME
- Name:
app.example.com - Value: NLB DNS name
Step 17: Validation
Confirm everything works as expected:
- SSH into Bastion Host
- SSH into private EC2 via Bastion
- Access EC2 via Session Manager
- Open browser: http://app.example.com
Expected Results
- Application loads successfully
- Auto Scaling responds to traffic
- Logs visible in CloudWatch
Final Thoughts
This project helped me deeply understand:
- Secure AWS network design
- Transit Gateway vs VPC peering
- Bastion-based access patterns
- Auto scaling in private subnets
- Observability and production readiness
Top comments (0)