Production-ready AWS infrastructure with EC2 Auto Scaling, Network Load Balancer, and multi-AZ high availability. Built with Terraform.
# 1. Clone repository
git clone https://github.com/engabelal/simple-webapp-ec2-nlb-asg.git
cd simple-webapp-ec2-nlb-asg/infra-iac
# 2. Initialize Terraform
terraform init
# 3. Deploy to dev
terraform apply -var-file="../envs/dev.tfvars"
# 4. Get NLB URL
terraform output nlb_dns_name
Feature | Description |
---|---|
ποΈ Multi-AZ Architecture | 2 Availability Zones for high availability |
β‘ Auto Scaling | Min: 2, Desired: 2, Max: 4 instances |
π Network Load Balancer | Layer 4 TCP load balancing |
π Private Subnets | EC2 instances isolated from internet |
π NAT Gateway per AZ | Redundant outbound internet access |
π¦ Multi-Environment | Separate dev/prod configurations |
π‘οΈ Security Groups | Minimal permissions, port 3000 only |
Component | Count | Configuration |
---|---|---|
Availability Zones | 2 | eu-north-1a, eu-north-1b |
Public Subnets | 2 | 10.0.1.0/24, 10.0.2.0/24 |
Private Subnets | 2 | 10.0.3.0/24, 10.0.4.0/24 |
NAT Gateways | 2 | One per AZ (high availability) |
EC2 Instances | 2-4 | Auto Scaling based on demand |
Load Balancer | 1 | Network Load Balancer (NLB) |
Node.js/Express server displaying instance metadata (ID, AZ, Region) and infrastructure components
βββββββββββββββ
β Internet β
ββββββββ¬βββββββ
β
ββββββββββββββββββββ΄βββββββββββββββββββ
β β
βββββββββ΄ββββββββββββββββββββββββββββββββββββββ΄ββββββββ
β AWS VPC (10.0.0.0/16) β
β β
β ββββββββββββββββββββββββββββββββββββββββββββββ β
β β Internet Gateway β β
β ββββββββββββββββββ¬ββββββββββββββββββββββββββββ β
β β β
β ββββββββββββββββββ΄ββββββββββββββββββββββββββββ β
β β Public Subnets (Multi-AZ) β β
β β ββββββββββββββββ ββββββββββββββββ β β
β β β Public-1 β β Public-2 β β β
β β β 10.0.1.0/24 β β 10.0.2.0/24 β β β
β β β (AZ-1a) β β (AZ-1b) β β β
β β β β β β β β
β β β ββββββββββββ β β ββββββββββββ β β β
β β β β NAT GW 1 β β β β NAT GW 2 β β β β
β β β ββββββββββββ β β ββββββββββββ β β β
β β ββββββββββββββββ ββββββββββββββββ β β
β ββββββββββββββββββ¬ββββββββββββββββββββββββββββ β
β β β
β ββββββββββββββββββ΄ββββββββββββββββββββββββββββ β
β β Network Load Balancer (Internet-facing) β β
β β Port 3000 (TCP) β β
β ββββββββββββββββββ¬ββββββββββββββββββββββββββββ β
β β β
β ββββββββββββββββββ΄ββββββββββββββββββββββββββββ β
β β Private Subnets (Multi-AZ) β β
β β ββββββββββββββββ ββββββββββββββββ β β
β β β Private-1 β β Private-2 β β β
β β β 10.0.3.0/24 β β 10.0.4.0/24 β β β
β β β (AZ-1a) β β (AZ-1b) β β β
β β β β β β β β
β β β ββββββββββββ β β ββββββββββββ β β β
β β β β EC2 β β β β EC2 β β β β
β β β β Node.js β β β β Node.js β β β β
β β β β Port 3000β β β β Port 3000β β β β
β β β ββββββββββββ β β ββββββββββββ β β β
β β ββββββββ¬ββββββββ ββββββββ¬ββββββββ β β
β β β β β β
β β βββββββββββββ¬ββββββββ β β
β β β β β
β β βββββββββββββ΄ββββββββββββ β β
β β β Auto Scaling Group β β β
β β β Min: 2 | Max: 4 β β β
β β βββββββββββββββββββββββββ β β
β ββββββββββββββββββββββββββββββββββββββββββββββ β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Inbound (User β App):
Internet β IGW β NLB (Public Subnets) β EC2 (Private Subnets)
Outbound (App β Internet):
EC2 (Private Subnets) β NAT Gateway (per AZ) β IGW β Internet
High Availability:
- β Each AZ has its own NAT Gateway
- β If one AZ fails, traffic routes to healthy AZ
- β No single point of failure
IaC | Terraform 1.8+ |
Cloud Provider | AWS (VPC, EC2, NLB, NAT Gateway, IGW) |
Compute | EC2 Auto Scaling Group (t3.micro) |
Load Balancer | Network Load Balancer (Layer 4) |
Runtime | Node.js 20.x |
Framework | Express.js 4.18 |
Scripting | Bash (bootstrap.sh) |
Networking | Multi-AZ VPC with public/private subnets |
simple-webapp-ec2-nlb-asg/
βββ infra-iac/
β βββ main.tf # Main configuration
β βββ provider.tf # AWS provider
β βββ variables.tf # Input variables
β βββ outputs.tf # Output values
β βββ networking.tf # VPC, Subnets, IGW, NAT GW
β βββ security.tf # Security Groups
β βββ compute.tf # Launch Template, ASG
β βββ loadbalancer.tf # NLB, Target Group, Listener
βββ envs/
β βββ dev.tfvars # Development config
β βββ prod.tfvars # Production config
βββ userdata/
β βββ bootstrap.sh # EC2 initialization script
βββ images/
β βββ result.png # Screenshot
βββ README.md
- β Terraform β₯ 1.8.0
- β AWS CLI configured
- β AWS Account with appropriate permissions
- β EC2 Key Pair (optional, for SSH access)
git clone https://github.com/engabelal/simple-webapp-ec2-nlb-asg.git
cd simple-webapp-ec2-nlb-asg
Edit envs/dev.tfvars
:
project_name = "simple-webapp"
environment = "dev"
region = "eu-north-1"
vpc_cidr = "10.0.0.0/16"
public_subnets = ["10.0.1.0/24", "10.0.2.0/24"]
private_subnets = ["10.0.3.0/24", "10.0.4.0/24"]
azs = ["eu-north-1a", "eu-north-1b"]
ami_id = "ami-04c08fd8aa14af291" # Amazon Linux 2023
key_pair_name = "your-key-pair" # Optional
cd infra-iac
terraform init
terraform plan -var-file="../envs/dev.tfvars"
terraform apply -var-file="../envs/dev.tfvars"
NLB_DNS=$(terraform output -raw nlb_dns_name)
echo "Application URL: http://$NLB_DNS"
cd infra-iac
terraform init
terraform apply -var-file="../envs/dev.tfvars"
Result: 2 instances, t3.micro, minimal cost
cd infra-iac
terraform init
terraform apply -var-file="../envs/prod.tfvars"
Result: 4 instances, t3.small, high availability
# Get NLB DNS
NLB_DNS=$(terraform output -raw nlb_dns_name)
# Multiple requests show different instances
for i in {1..10}; do
curl http://$NLB_DNS | grep "Instance ID"
done
# Terminate an instance
aws ec2 terminate-instances --instance-ids <instance-id>
# Watch ASG replace it automatically
watch -n 5 'aws autoscaling describe-auto-scaling-groups \
--auto-scaling-group-names simple-webapp-dev-asg \
--query "AutoScalingGroups[0].Instances[*].[InstanceId,HealthStatus]" \
--output table'
cd infra-iac
terraform destroy -var-file="../envs/dev.tfvars"
Component | Quantity | Unit Cost | Total |
---|---|---|---|
EC2 (t3.micro) | 2 | $7.50 | $15.00 |
Network Load Balancer | 1 | $16.20 | $16.20 |
NAT Gateway | 2 | $32.40 | $64.80 |
Data Transfer | Variable | - | ~$5.00 |
EBS Storage | 16 GB | $0.08/GB | $1.28 |
Total | - | - | ~$102/month |
Component | Quantity | Unit Cost | Total |
---|---|---|---|
EC2 (t3.small) | 4 | $15.00 | $60.00 |
Network Load Balancer | 1 | $16.20 | $16.20 |
NAT Gateway | 2 | $32.40 | $64.80 |
Data Transfer | Variable | - | ~$10.00 |
EBS Storage | 32 GB | $0.08/GB | $2.56 |
Total | - | - | ~$153/month |
π‘ Cost Optimization Tips:
- Use Spot Instances for dev (save 70%)
- Schedule scaling (scale down at night)
- Use single NAT Gateway for dev (save $32)
- Enable S3 VPC Endpoint (free data transfer)
Issue | Solution |
---|---|
NLB not accessible | β’ Check security group allows port 3000 β’ Verify target health in AWS Console β’ Wait 2-3 minutes for NLB to be active |
Instances unhealthy | β’ Check user data script logs: /var/log/user-data.log β’ Verify Node.js app is running: systemctl status nodejs-app β’ Check security group allows NLB traffic on port 3000 |
Terraform state locked | terraform force-unlock <LOCK_ID> |
AMI not found | Update ami_id in tfvars for your region |
NAT Gateway timeout | β’ Check route tables β’ Verify NAT GW has Elastic IP β’ Check NACL rules |
Auto Scaling not working | β’ Check ASG health check settings β’ Verify launch template is valid β’ Check CloudWatch logs |
NLB_DNS=$(terraform output -raw nlb_dns_name)
# Should show different instance IDs
for i in {1..20}; do
curl -s http://$NLB_DNS | grep "Instance ID"
sleep 1
done
# Terminate instance in AZ-1a
aws ec2 terminate-instances --instance-ids <instance-id-az1a>
# Traffic should continue via AZ-1b
curl http://$NLB_DNS
# Watch ASG maintain desired count
watch -n 5 'aws autoscaling describe-auto-scaling-groups \
--auto-scaling-group-names simple-webapp-dev-asg \
--query "AutoScalingGroups[0].[DesiredCapacity,MinSize,MaxSize]" \
--output table'
π Why NAT Gateway per AZ?
Single NAT Gateway (Not Recommended):
AZ-1a: EC2 β NAT GW (AZ-1a) β Internet β
AZ-1b: EC2 β NAT GW (AZ-1a) β Internet β (Cross-AZ traffic)
- β Single point of failure
- β Cross-AZ data transfer costs
- β If AZ-1a fails, AZ-1b loses internet
NAT Gateway per AZ (Recommended):
AZ-1a: EC2 β NAT GW (AZ-1a) β Internet β
AZ-1b: EC2 β NAT GW (AZ-1b) β Internet β
- β No single point of failure
- β No cross-AZ data transfer
- β Full redundancy
Cost vs Reliability:
- Extra cost: ~$32/month per NAT GW
- Benefit: 99.99% availability
- Production: Always use NAT GW per AZ
- Development: Can use single NAT GW to save cost
ποΈ Infrastructure Components Explained
- VPC: Isolated network (10.0.0.0/16)
- Public Subnets: Host NLB and NAT Gateways
- Private Subnets: Host EC2 instances (no direct internet)
- Internet Gateway: Public internet access
- NAT Gateways: Outbound internet for private instances
- Route Tables: Traffic routing rules
- Launch Template: EC2 configuration blueprint
- Auto Scaling Group: Maintains instance count
- User Data: Bootstrap script (installs Apache)
- Network Load Balancer: Layer 4 (TCP) load balancing
- Target Group: Health checks and routing
- Listener: Port 80 traffic forwarding
- Security Group: Firewall rules
- Inbound: Port 3000 from anywhere
- Outbound: All traffic allowed
π Security Best Practices
- β EC2 instances in private subnets
- β No public IPs on instances
- β Security groups with minimal permissions (port 3000 only)
- β NAT Gateway for outbound only
- β Multi-AZ for high availability
- β Non-privileged port (3000) for Node.js
- β Systemd service for auto-restart
- π Enable VPC Flow Logs
- π Add WAF to NLB
- π Implement HTTPS (ALB + ACM)
- π Use AWS Secrets Manager
- π Enable CloudTrail
- π Add CloudWatch Alarms
π Monitoring & Alerts
- EC2 CPU Utilization
- Network In/Out
- Target Health Count
- NLB Active Connections
- NAT Gateway Bytes Processed
# High CPU alarm
aws cloudwatch put-metric-alarm \
--alarm-name high-cpu \
--metric-name CPUUtilization \
--threshold 80 \
--comparison-operator GreaterThanThreshold
# Unhealthy targets
aws cloudwatch put-metric-alarm \
--alarm-name unhealthy-targets \
--metric-name UnHealthyHostCount \
--threshold 1 \
--comparison-operator GreaterThanOrEqualToThreshold
After completing this project, you'll understand:
β
Multi-AZ Architecture - High availability design patterns
β
Auto Scaling - Automatic capacity management
β
Load Balancing - Traffic distribution strategies
β
VPC Networking - Public/private subnet design
β
NAT Gateway - Outbound internet for private resources
β
Security Groups - Network-level security
β
Infrastructure as Code - Terraform best practices
β
Cost Optimization - AWS pricing and optimization
cd infra-iac
terraform destroy -var-file="../envs/dev.tfvars"
# Check no resources remain
aws ec2 describe-instances --filters "Name=tag:Project,Values=simple-webapp"
aws elbv2 describe-load-balancers --names simple-webapp-dev-nlb
aws ec2 describe-nat-gateways --filter "Name=tag:Project,Values=simple-webapp"
MIT License - Free to use for learning and production!
Ahmed Belal - DevOps & Cloud Engineer
π GitHub β’ LinkedIn β’ Email
β Star this repo if you found it helpful!
π Found a bug? Open an issue
π‘ Have suggestions? Create a pull request
π§ Questions? Reach out via email
Built with β€οΈ by Ahmed Belal | ABCloudOps