AWS Networking: Zero to Hero
Complete Guide to Amazon Virtual Private Cloud (VPC) and Networking Services
Table of Contents
- 1. Introduction to AWS Networking
- 2. VPC Basics
- 3. Subnets
- 4. Internet Gateway
- 5. NAT Gateway & NAT Instance
- 6. Route Tables
- 7. Security Groups
- 8. Network ACLs
- 9. VPC Peering
- 10. VPC Endpoints
- 11. Transit Gateway
- 12. VPN Connections
- 13. Best Practices
- 14. Case Studies & Networking Playbooks
- 15. Technical Q&A for DevOps & Cloud Engineers
Quick Start Guide
New to AWS Networking? Follow this order:
- Read VPC Basics section – understand what VPC is
- Read Subnets section – learn public vs private
- Read Internet Gateway section – for Internet access
- Read Route Tables section – how routing works
- Read Security Groups section – your firewall
- Then jump to Scenario 1 to build your first VPC
2. VPC (Virtual Private Cloud)
Your isolated network environment in AWS
- Always! Every AWS resource runs in a VPC (or default VPC)
- For production applications – you need control over network design
- When you need multiple environments (dev, staging, prod)
- For security compliance – isolate resources
1. How to Choose CIDR Block (IP Range)
What is CIDR? CIDR (Classless Inter-Domain Routing) defines your IP address range. Format: X.X.X.X/XX
Common Choices:
- 10.0.0.0/16 – Most popular (65,536 IPs) – Best for most use cases
- 10.0.0.0/20 – Medium (4,096 IPs) – Good for smaller projects
- 172.16.0.0/16 – Alternative to 10.x.x.x
- 192.168.0.0/16 – Another alternative
How to Choose:
- Check for conflicts: If connecting to on-premises network, ensure CIDR doesn’t overlap with your office network
- Plan for growth: Use /16 (10.0.0.0/16) to have room for many subnets
- Multiple VPCs: If you’ll have multiple VPCs, use different ranges:
- VPC 1: 10.0.0.0/16
- VPC 2: 10.1.0.0/16
- VPC 3: 10.2.0.0/16
- Avoid these: Don’t use 172.31.0.0/16 (used by default VPC) or ranges that conflict with AWS services
2. How to Decide Which Region and Availability Zones
Region Selection:
- User Location: Choose region closest to your users for lower latency
- US users → us-east-1 (N. Virginia) or us-west-2 (Oregon)
- Europe → eu-west-1 (Ireland) or eu-central-1 (Frankfurt)
- Asia → ap-southeast-1 (Singapore) or ap-northeast-1 (Tokyo)
- Service Availability: Some AWS services are only in certain regions – check if you need specific services
- Compliance: Some regions have data residency requirements (e.g., EU for GDPR)
- Cost: Different regions have different pricing – us-east-1 is usually cheapest
- Disaster Recovery: Consider having VPCs in multiple regions for DR
Availability Zone Selection:
- High Availability: Always use at least 2 AZs (e.g., us-east-1a and us-east-1b)
- If one AZ fails, your application stays running
- Required for production workloads
- For Production: Use 3 AZs for maximum availability
- AZ Names: AWS assigns random names (a, b, c) – don’t hardcode them
- us-east-1a in your account ≠ us-east-1a in another account
- Use AZ IDs instead if you need consistency
- Subnet Distribution: Create subnets in different AZs:
- Public subnet in us-east-1a
- Public subnet in us-east-1b
- Private subnet in us-east-1a
- Private subnet in us-east-1b
3. How to Plan Your Subnet Structure
Subnet Planning Strategy:
Step 1: Determine Your Architecture
- Simple App: 1 public subnet (web server) + 1 private subnet (database)
- 3-Tier App: Public (ALB) + App Private (EC2) + DB Private (RDS)
- Microservices: Public (ALB) + Multiple private subnets per service
Step 2: Calculate Subnet Sizes
Subnet Size Guide:
- /24 (256 IPs, 251 usable): Most common – good for most subnets
- /26 (64 IPs, 59 usable): Small subnets – for NAT Gateway, bastion hosts
- /28 (16 IPs, 11 usable): Very small – rarely used
- /22 (1,024 IPs): Large – for many resources
Remember: AWS reserves 5 IPs per subnet (first 4 + last 1)
Step 3: Plan Your CIDR Allocation
Example: VPC with 10.0.0.0/16
| Subnet | CIDR | AZ | Purpose |
|---|---|---|---|
| Public 1 | 10.0.1.0/24 | us-east-1a | ALB, NAT Gateway |
| Public 2 | 10.0.2.0/24 | us-east-1b | ALB, NAT Gateway |
| Private App 1 | 10.0.11.0/24 | us-east-1a | EC2 Instances |
| Private App 2 | 10.0.12.0/24 | us-east-1b | EC2 Instances |
| Private DB 1 | 10.0.21.0/24 | us-east-1a | RDS Database |
| Private DB 2 | 10.0.22.0/24 | us-east-1b | RDS Database |
Pattern: Use 10.0.X.0/24 where X = 1-2 (public), 11-12 (app), 21-22 (db). This keeps it organized!
Step 4: Checklist Before Creating
- ✅ VPC CIDR chosen (e.g., 10.0.0.0/16)
- ✅ Region selected (closest to users)
- ✅ At least 2 AZs identified
- ✅ Subnet sizes calculated (/24 recommended)
- ✅ Subnet CIDRs planned (no overlaps!)
- ✅ Public vs private subnets identified
- ✅ Room for future growth (don’t use all IPs)
3. Subnets
- Always! You need at least one subnet to launch resources
- For high availability – create subnets in multiple AZs
- For security – separate public (Internet-facing) and private (internal) resources
- For organization – group related resources together
Public vs Private Subnets – The Key Difference
| Feature | Public Subnet | Private Subnet |
|---|---|---|
| Internet Access | Direct via Internet Gateway | Via NAT Gateway (outbound only) |
| Public IP | Can have public IPs | No public IPs |
| Route Table | 0.0.0.0/0 → Internet Gateway | 0.0.0.0/0 → NAT Gateway |
| Use Cases | Load Balancers, NAT Gateways, Bastion Hosts | Application Servers, Databases, Internal Services |
| Security | Exposed to Internet | Isolated from Internet |
- Your VPC CIDR block (e.g., 10.0.0.0/16)
- Which Availability Zones you’ll use (at least 2 for HA)
- How many resources you’ll have (determines subnet size)
- Subnet size: /24 (256 IPs) is most common
📍 How to Add Availability Zones
Understanding Availability Zones: Availability Zones (AZs) are physically separate data centers within a region. AWS automatically provides multiple AZs per region (typically 3-6). You don’t “create” AZs – they’re pre-configured by AWS.
Method 1: Using AWS Console
- View Available AZs: Go to VPC Dashboard → Subnets → Create subnet
- See AZ List: In the “Availability Zone” dropdown, you’ll see all available AZs (e.g., us-east-1a, us-east-1b, us-east-1c)
- Select AZ: Choose an AZ that you haven’t used yet for your subnets
- Create Subnet: Create a new subnet in the selected AZ
Method 2: Using AWS CLI
aws ec2 describe-availability-zones –region us-east-1
# Output will show:
# {
# “AvailabilityZones”: [
# { “ZoneName”: “us-east-1a”, “State”: “available” },
# { “ZoneName”: “us-east-1b”, “State”: “available” },
# { “ZoneName”: “us-east-1c”, “State”: “available” }
# ]
# }
# Create subnet in a specific AZ
aws ec2 create-subnet \
–vpc-id vpc-1234567890abcdef0 \
–cidr-block 10.0.3.0/24 \
–availability-zone us-east-1c
Method 3: Using Terraform
data “aws_availability_zones” “available” {
state = “available”
}
# Create subnet in third AZ
resource “aws_subnet” “public_c” {
vpc_id = aws_vpc.main.id
cidr_block = “10.0.3.0/24”
availability_zone = data.aws_availability_zones.available.names[2] # Third AZ
map_public_ip_on_launch = true
tags = {
Name = “public-subnet-c”
}
}
# Or specify AZ directly
resource “aws_subnet” “public_c_direct” {
vpc_id = aws_vpc.main.id
cidr_block = “10.0.3.0/24”
availability_zone = “us-east-1c” # Direct AZ name
map_public_ip_on_launch = true
tags = {
Name = “public-subnet-c”
}
}
⚠️ Important Notes:
- AZ Names Vary: AZ names (a, b, c) are randomized per AWS account. Your “us-east-1a” might be different from another account’s “us-east-1a”.
- Minimum 2 AZs: For high availability, always use at least 2 AZs. For production, use 3 AZs.
- AZ Limits: Most regions have 3-6 AZs. Check AWS documentation for your specific region.
- Cost: No additional cost for using multiple AZs, but cross-AZ data transfer costs $0.01/GB.
- Subnet per AZ: Each subnet must be in exactly one AZ. Create separate subnets for each AZ you want to use.
🔄 How Subnets Communicate With Each Other
Scenario: EC2 in Subnet A needs to connect to Database in Subnet B
✅ How It Works (Automatic!):
- Same VPC: Both subnets are in the same VPC (10.0.0.0/16)
- Local Route: Every route table has a “local” route (10.0.0.0/16 → local) – this is automatic!
- Direct Communication: EC2 (10.0.11.10) can directly connect to RDS (10.0.12.50)
- No Internet/NAT Needed: Traffic stays within VPC – fast and secure!
- Security Groups: Make sure Security Groups allow the connection (e.g., RDS SG allows port 5432 from EC2 SG)
📋 Route Table Configuration:
Subnet A Route Table:
| Destination | Target |
|---|---|
| 10.0.0.0/16 | Local (automatic) |
| 0.0.0.0/0 | NAT Gateway (for Internet) |
Subnet B Route Table:
| Destination | Target |
|---|---|
| 10.0.0.0/16 | Local (automatic) |
| 0.0.0.0/0 | NAT Gateway (for Internet) |
Key Point: The “Local” route (10.0.0.0/16 → local) is automatically added to every route table. This enables all subnets in the VPC to communicate with each other!
⚠️ Important Requirements:
- Security Groups: Must allow traffic between subnets
- EC2 Security Group: Allow outbound to RDS
- RDS Security Group: Allow inbound port 5432 from EC2 Security Group
- Network ACLs: If using custom NACLs, ensure they allow traffic (default NACL allows all)
- Same VPC: Subnets must be in the same VPC (different VPCs need peering/Transit Gateway)
💡 Real-World Example:
Connection Flow:
- EC2 instance (10.0.11.10) in Subnet A wants to connect to RDS (10.0.12.50) in Subnet B
- EC2 checks its route table: “Where is 10.0.12.50?”
- Route table matches: 10.0.0.0/16 → Local (covers 10.0.12.50)
- Traffic routes directly within VPC (no Internet, no NAT)
- Security Group on RDS checks: “Is this from allowed source?”
- If allowed → Connection successful! ✅
🎯 Key Takeaways:
- ✅ Subnets in same VPC can communicate automatically via “Local” route
- ✅ No Internet Gateway or NAT Gateway needed for subnet-to-subnet communication
- ✅ Traffic stays within AWS network (fast, secure, no data charges)
- ✅ Security Groups control what traffic is allowed
- ✅ Works across different Availability Zones (Subnet A in AZ-1, Subnet B in AZ-2)
4. Internet Gateway (IGW)
- Internet Gateway is attached to your VPC
- Resources in public subnets can have public IP addresses
- Route table routes 0.0.0.0/0 to Internet Gateway
- Provides bidirectional Internet access (inbound and outbound)
- No bandwidth charges for data transferred through IGW
- Public-facing applications: Web servers, load balancers that need Internet access
- Resources in public subnets: Any resource that needs direct Internet connectivity
- Bastion hosts: For SSH access from Internet
- NAT Gateway: NAT Gateway needs IGW (placed in public subnet)
- NOT needed for: Resources in private subnets only (they use NAT Gateway instead)
- You must have a VPC created first
- Only one IGW per VPC (but can be shared across VPCs via RAM)
- IGW is free – no charges
- IGW is highly available – no single point of failure
Method 1: Using AWS Console (GUI)
- Go to AWS Console
- Search for “VPC” or go to Services → Networking & Content Delivery → VPC
- In left sidebar, click “Internet Gateways”
- Click “Create Internet Gateway” button (top right)
- Name tag: Enter a name (e.g., “my-vpc-igw”)
- Leave other settings as default
- Click “Create Internet Gateway”
- After creation, you’ll see the IGW in “Detached” state
- Select the IGW you just created
- Click “Actions” → “Attach to VPC”
- Select your VPC from the dropdown
- Click “Attach Internet Gateway”
- Check the IGW status – should show “Attached”
- Verify the VPC ID matches your VPC
- Go to “Route Tables” in left sidebar
- Select the route table for your public subnet
- Click “Edit routes”
- Click “Add route”
- Destination: 0.0.0.0/0
- Target: Select “Internet Gateway” → Choose your IGW
- Click “Save changes”
Method 2: Using AWS CLI
aws ec2 create-internet-gateway –tag-specifications ‘ResourceType=internet-gateway,Tags=[{Key=Name,Value=my-vpc-igw}]’
# Note the InternetGatewayId from output (e.g., igw-1234567890abcdef0)
# Step 2: Attach to VPC (replace igw-xxx with your IGW ID, vpc-xxx with your VPC ID)
aws ec2 attach-internet-gateway –internet-gateway-id igw-1234567890abcdef0 –vpc-id vpc-0987654321abcdef0
# Step 3: Update Route Table (replace rtb-xxx with your route table ID)
aws ec2 create-route –route-table-id rtb-1234567890abcdef0 –destination-cidr-block 0.0.0.0/0 –gateway-id igw-1234567890abcdef0
Method 3: Using Terraform
resource “aws_internet_gateway” “main” {
vpc_id = aws_vpc.main.id
tags = {
Name = “my-vpc-igw”
}
}
# Update Route Table
resource “aws_route” “public_internet_gateway” {
route_table_id = aws_route_table.public.id
destination_cidr_block = “0.0.0.0/0”
gateway_id = aws_internet_gateway.main.id
}
⚠️ Important Notes:
- Route Table Update is Required: Just creating and attaching IGW isn’t enough! You must add route 0.0.0.0/0 → IGW to your route table
- Public Subnets Only: IGW route should only be in public subnet route tables, not private
- One IGW per VPC: You can only attach one IGW to a VPC at a time
- Detach Before Delete: You must detach IGW before you can delete it
5. NAT Gateway
- NAT Gateway is placed in a public subnet (needs Internet Gateway)
- Private subnet route table routes 0.0.0.0/0 to NAT Gateway
- NAT Gateway has an Elastic IP address (required)
- Only outbound traffic is allowed (Internet cannot initiate connections)
- Instances in private subnet can access Internet for updates, API calls, etc.
- Private subnets need Internet access: For downloading updates, calling external APIs, pulling Docker images from Docker Hub
- Security requirement: You want resources in private subnets (no direct Internet exposure) but they need outbound Internet
- Production applications: Application servers in private subnets that need to call third-party APIs
- NOT needed if: Resources in private subnets don’t need Internet access (use VPC Endpoints for AWS services instead)
- ✅ VPC created
- ✅ Internet Gateway created and attached to VPC
- ✅ At least one public subnet (NAT Gateway goes in public subnet)
- ✅ Elastic IP address (AWS will allocate one if you don’t have one)
- ✅ Private subnets that need Internet access
NAT Gateway vs NAT Instance
| Feature | NAT Gateway | NAT Instance |
|---|---|---|
| Type | Managed service | EC2 instance |
| Availability | Highly available (per AZ) | Single point of failure |
| Maintenance | Fully managed by AWS | You manage (patches, updates) |
| Bandwidth | Up to 45 Gbps | Depends on instance type |
| Cost | $0.045/hour + data transfer | EC2 instance cost |
| Elastic IP | Required | Required |
| Security Groups | Managed by AWS | You configure |
| Use Case | Production (recommended) | Development, testing |
Method 1: Using AWS Console (GUI)
- Go to AWS Console → VPC Dashboard
- In left sidebar, click “NAT Gateways”
- Click “Create NAT Gateway” button (top right)
- Name tag: Enter a name (e.g., “nat-gateway-1a”)
- Subnet: Select a PUBLIC subnet (important! NAT Gateway must be in public subnet)
- Elastic IP allocation ID:
- If you have an Elastic IP: Select “Use an existing Elastic IP” and choose it
- If you don’t have one: Select “Allocate Elastic IP” (AWS will create one for you)
- Click “Create NAT Gateway”
- Wait 1-2 minutes for NAT Gateway to become available
- Status will change from “Pending” to “Available”
- Go to “Route Tables” in left sidebar
- Select the route table for your PRIVATE subnet
- Click “Edit routes”
- Click “Add route”
- Destination: 0.0.0.0/0
- Target: Select “NAT Gateway” → Choose your NAT Gateway
- Click “Save changes”
- Repeat steps 1-4 for each Availability Zone
- Create NAT Gateway in each public subnet (one per AZ)
- Update each private subnet’s route table to use NAT Gateway in same AZ
- Example: Private subnet in us-east-1a → NAT Gateway in us-east-1a
Method 2: Using AWS CLI
aws ec2 allocate-address –domain vpc
# Note the AllocationId from output (e.g., eipalloc-1234567890abcdef0)
# Step 2: Create NAT Gateway
# Replace subnet-xxx with your PUBLIC subnet ID
# Replace eipalloc-xxx with your Elastic IP allocation ID
aws ec2 create-nat-gateway \
–subnet-id subnet-1234567890abcdef0 \
–allocation-id eipalloc-1234567890abcdef0 \
–tag-specifications ‘ResourceType=nat-gateway,Tags=[{Key=Name,Value=nat-gateway-1a}]’
# Note the NatGatewayId from output (e.g., nat-1234567890abcdef0)
# Step 3: Wait for NAT Gateway to be available (takes 1-2 minutes)
aws ec2 wait nat-gateway-available –nat-gateway-ids nat-1234567890abcdef0
# Step 4: Update Route Table (replace rtb-xxx with your private subnet route table ID)
aws ec2 create-route \
–route-table-id rtb-1234567890abcdef0 \
–destination-cidr-block 0.0.0.0/0 \
–nat-gateway-id nat-1234567890abcdef0
Method 3: Using Terraform
resource “aws_eip” “nat” {
domain = “vpc”
tags = {
Name = “nat-gateway-eip”
}
}
# Create NAT Gateway
resource “aws_nat_gateway” “main” {
allocation_id = aws_eip.nat.id
subnet_id = aws_subnet.public.id # Must be public subnet!
tags = {
Name = “nat-gateway-1a”
}
depends_on = [aws_internet_gateway.main]
}
# Update Private Subnet Route Table
resource “aws_route” “private_nat” {
route_table_id = aws_route_table.private.id
destination_cidr_block = “0.0.0.0/0”
nat_gateway_id = aws_nat_gateway.main.id
}
⚠️ Important Notes:
- Must be in Public Subnet: NAT Gateway MUST be placed in a public subnet (one that has Internet Gateway route)
- Elastic IP Required: NAT Gateway needs an Elastic IP – AWS can allocate one for you
- Route Table Update is Required: Creating NAT Gateway isn’t enough! You must update private subnet route table with 0.0.0.0/0 → NAT Gateway
- High Availability: For production, create one NAT Gateway per AZ and route each private subnet to NAT in same AZ
- Cost: $0.045/hour per NAT Gateway + data transfer charges ($0.045/GB)
- Internet Gateway Required: NAT Gateway needs Internet Gateway to work (placed in public subnet that has IGW)
✅ High Availability Setup (Production Best Practice):
For 2 Availability Zones:
- Create NAT Gateway in public subnet us-east-1a
- Create NAT Gateway in public subnet us-east-1b
- Private subnet in us-east-1a → Route to NAT Gateway in us-east-1a
- Private subnet in us-east-1b → Route to NAT Gateway in us-east-1b
- If one AZ fails, the other continues working
Benefit: No cross-AZ data charges, better performance, high availability
6. Route Tables
Public Subnet Route Table
| Destination | Target | Description |
|---|---|---|
| 10.0.0.0/16 | Local | Traffic within VPC (automatic) |
| 0.0.0.0/0 | Internet Gateway | All other traffic to Internet |
Private Subnet Route Table
| Destination | Target | Description |
|---|---|---|
| 10.0.0.0/16 | Local | Traffic within VPC (automatic) |
| 0.0.0.0/0 | NAT Gateway | All other traffic via NAT |
- Always! Every subnet must have a route table (VPC comes with a default one)
- Public subnets: Need route to Internet Gateway (0.0.0.0/0 → IGW)
- Private subnets: Need route to NAT Gateway (0.0.0.0/0 → NAT) or no Internet route
- Custom routing: When you need different routing for different subnets
- VPC Peering: Need routes to peer VPCs
- You have a VPC created
- You have subnets created
- For public subnets: Internet Gateway must be created and attached
- For private subnets: NAT Gateway must be created (if you want Internet access)
- VPC automatically creates a “main” route table (you can use it or create custom ones)
Method 1: Using AWS Console (GUI)
- Go to AWS Console → VPC Dashboard
- In left sidebar, click “Route Tables”
- You’ll see the default “main” route table (created with VPC)
- Click “Create route table” button (top right)
- Name tag: Enter a name (e.g., “public-route-table” or “private-route-table”)
- VPC: Select your VPC
- Click “Create route table”
- Select your route table
- Click “Routes” tab (at bottom)
- You’ll see the “Local” route (10.0.0.0/16 → local) – this is automatic, don’t delete it!
- Click “Edit routes” → “Add route”
- For Public Subnet Route Table:
- Destination: 0.0.0.0/0
- Target: Select “Internet Gateway” → Choose your IGW
- For Private Subnet Route Table:
- Destination: 0.0.0.0/0
- Target: Select “NAT Gateway” → Choose your NAT Gateway
- Click “Save changes”
- Select your route table
- Click “Subnet associations” tab
- Click “Edit subnet associations”
- Check the subnets you want to associate with this route table
- Click “Save associations”
- Note: Each subnet can only be associated with ONE route table at a time
- Check “Routes” tab – should show Local route + your custom route
- Check “Subnet associations” tab – should show your subnets
- Test connectivity from resources in those subnets
Method 2: Using AWS CLI
# Replace vpc-xxx with your VPC ID
aws ec2 create-route-table –vpc-id vpc-1234567890abcdef0 –tag-specifications ‘ResourceType=route-table,Tags=[{Key=Name,Value=public-route-table}]’
# Note the RouteTableId from output (e.g., rtb-1234567890abcdef0)
# Step 2: Add Route to Internet Gateway (for public subnet)
# Replace rtb-xxx with your route table ID
# Replace igw-xxx with your Internet Gateway ID
aws ec2 create-route \
–route-table-id rtb-1234567890abcdef0 \
–destination-cidr-block 0.0.0.0/0 \
–gateway-id igw-1234567890abcdef0
# Step 3: Add Route to NAT Gateway (for private subnet)
# Replace nat-xxx with your NAT Gateway ID
aws ec2 create-route \
–route-table-id rtb-0987654321fedcba0 \
–destination-cidr-block 0.0.0.0/0 \
–nat-gateway-id nat-1234567890abcdef0
# Step 4: Associate Subnet with Route Table
# Replace subnet-xxx with your subnet ID
aws ec2 associate-route-table \
–route-table-id rtb-1234567890abcdef0 \
–subnet-id subnet-1234567890abcdef0
Method 3: Using Terraform
resource “aws_route_table” “public” {
vpc_id = aws_vpc.main.id
route {
cidr_block = “0.0.0.0/0”
gateway_id = aws_internet_gateway.main.id
}
tags = {
Name = “public-route-table”
}
}
# Private Subnet Route Table
resource “aws_route_table” “private” {
vpc_id = aws_vpc.main.id
route {
cidr_block = “0.0.0.0/0”
nat_gateway_id = aws_nat_gateway.main.id
}
tags = {
Name = “private-route-table”
}
}
# Associate Subnet with Route Table
resource “aws_route_table_association” “public” {
subnet_id = aws_subnet.public.id
route_table_id = aws_route_table.public.id
}
resource “aws_route_table_association” “private” {
subnet_id = aws_subnet.private.id
route_table_id = aws_route_table.private.id
}
⚠️ Important Notes:
- Local Route is Automatic: Every route table automatically has a “Local” route (VPC CIDR → local). Don’t delete it! This enables subnet-to-subnet communication.
- Route Evaluation: Routes are evaluated using longest prefix match (most specific route wins). 10.0.1.0/24 is more specific than 10.0.0.0/16.
- Default Route (0.0.0.0/0): This matches ALL traffic not matched by other routes. Use it for Internet Gateway or NAT Gateway.
- One Route Table per Subnet: Each subnet can only be associated with ONE route table at a time.
- Multiple Subnets: Multiple subnets can share the same route table (common for public subnets or private subnets).
- Main Route Table: VPC creates a “main” route table. If you don’t explicitly associate a subnet, it uses the main route table.
✅ Common Route Table Configurations:
1. Public Subnet Route Table:
- 10.0.0.0/16 → Local (automatic)
- 0.0.0.0/0 → Internet Gateway
- Result: Subnet can access Internet directly
2. Private Subnet Route Table (with Internet access):
- 10.0.0.0/16 → Local (automatic)
- 0.0.0.0/0 → NAT Gateway
- Result: Subnet can access Internet via NAT (outbound only)
3. Private Subnet Route Table (no Internet access):
- 10.0.0.0/16 → Local (automatic)
- No 0.0.0.0/0 route
- Result: Subnet can only communicate within VPC (most secure)
7. Security Groups
Example: Web Server Security Group
| Type | Protocol | Port Range | Source | Description |
|---|---|---|---|---|
| Inbound | HTTP | 80 | 0.0.0.0/0 | Allow HTTP from Internet |
| Inbound | HTTPS | 443 | 0.0.0.0/0 | Allow HTTPS from Internet |
| Inbound | SSH | 22 | 10.0.0.0/16 | Allow SSH from VPC |
| Outbound | All | All | 0.0.0.0/0 | Allow all outbound (default) |
- Always! Every EC2 instance, RDS database, and many AWS services require security groups
- EC2 instances: Control SSH, HTTP, HTTPS, and application port access
- RDS databases: Control database port access (3306 for MySQL, 5432 for PostgreSQL)
- Load balancers: Control which ports are accessible
- Security: Implement least privilege – only allow necessary traffic
- You have a VPC created (security groups are VPC-specific)
- Which ports your application needs (HTTP: 80, HTTPS: 443, SSH: 22, etc.)
- Who needs access (Internet: 0.0.0.0/0, VPC: 10.0.0.0/16, specific IP, or another security group)
- Default behavior: All inbound denied, all outbound allowed
Security Group Key Characteristics
- Stateful: If you allow inbound traffic, return traffic is automatically allowed (you don’t need outbound rule)
- Default Deny Inbound: All inbound traffic is denied by default – you must explicitly allow
- Default Allow Outbound: All outbound traffic is allowed by default
- Instance Level: Applied at the instance level (not subnet level like NACLs)
- Multiple SGs: An instance can have up to 5 security groups (all rules are combined)
- Allow Rules Only: You can only add allow rules, not deny rules
Method 1: Using AWS Console (GUI)
- Go to AWS Console → EC2 Dashboard or VPC Dashboard
- In left sidebar, click “Security Groups”
- Click “Create security group” button (top right)
- Security group name: Enter a descriptive name (e.g., “web-server-sg”)
- Description: Enter description (e.g., “Security group for web servers”)
- VPC: Select your VPC (security groups are VPC-specific)
- Click “Add rule” under Inbound rules
- Type: Select protocol (HTTP, HTTPS, SSH, Custom TCP, etc.)
- Port range: Enter port (e.g., 80 for HTTP, 443 for HTTPS, 22 for SSH)
- Source: Choose source:
- Anywhere-IPv4 (0.0.0.0/0): Allow from Internet
- My IP: Allow only from your current IP
- Custom: Enter CIDR (e.g., 10.0.0.0/16 for VPC)
- Security group: Select another security group ID (best practice!)
- Description: Add description (e.g., “Allow HTTP from Internet”)
- Click “Add rule” again for more rules
- By default, all outbound traffic is allowed
- If you need to restrict outbound, click “Add rule” under Outbound rules
- Configure similar to inbound rules
- Note: For most cases, default outbound (allow all) is fine
- Review your rules
- Click “Create security group”
- Note the Security Group ID (e.g., sg-1234567890abcdef0)
- When launching EC2 instance, select your security group
- Or edit existing instance: Select instance → Security tab → Change security groups
- An instance can have multiple security groups (up to 5)
Method 2: Using AWS CLI
# Replace vpc-xxx with your VPC ID
aws ec2 create-security-group \
–group-name web-server-sg \
–description “Security group for web servers” \
–vpc-id vpc-1234567890abcdef0
# Note the GroupId from output (e.g., sg-1234567890abcdef0)
# Step 2: Add Inbound Rule (HTTP from Internet)
aws ec2 authorize-security-group-ingress \
–group-id sg-1234567890abcdef0 \
–protocol tcp \
–port 80 \
–cidr 0.0.0.0/0
# Step 3: Add Inbound Rule (HTTPS from Internet)
aws ec2 authorize-security-group-ingress \
–group-id sg-1234567890abcdef0 \
–protocol tcp \
–port 443 \
–cidr 0.0.0.0/0
# Step 4: Add Inbound Rule (SSH from VPC)
aws ec2 authorize-security-group-ingress \
–group-id sg-1234567890abcdef0 \
–protocol tcp \
–port 22 \
–cidr 10.0.0.0/16
# Step 5: Add Inbound Rule (Allow from another Security Group)
aws ec2 authorize-security-group-ingress \
–group-id sg-0987654321fedcba0 \
–protocol tcp \
–port 3306 \
–source-group sg-1234567890abcdef0
Method 3: Using Terraform
resource “aws_security_group” “web” {
name = “web-server-sg”
description = “Security group for web servers”
vpc_id = aws_vpc.main.id
# Inbound: HTTP from Internet
ingress {
from_port = 80
to_port = 80
protocol = “tcp”
cidr_blocks = [“0.0.0.0/0”]
description = “Allow HTTP from Internet”
}
# Inbound: HTTPS from Internet
ingress {
from_port = 443
to_port = 443
protocol = “tcp”
cidr_blocks = [“0.0.0.0/0”]
description = “Allow HTTPS from Internet”
}
# Inbound: SSH from VPC
ingress {
from_port = 22
to_port = 22
protocol = “tcp”
cidr_blocks = [aws_vpc.main.cidr_block]
description = “Allow SSH from VPC”
}
# Outbound: Allow all (default)
egress {
from_port = 0
to_port = 65535
protocol = “-1”
cidr_blocks = [“0.0.0.0/0”]
}
tags = {
Name = “web-server-sg”
}
}
# Database Security Group (allows from Web SG)
resource “aws_security_group” “db” {
name = “database-sg”
description = “Security group for database”
vpc_id = aws_vpc.main.id
# Inbound: MySQL from Web Security Group
ingress {
from_port = 3306
to_port = 3306
protocol = “tcp”
security_groups = [aws_security_group.web.id]
description = “Allow MySQL from web servers”
}
}
⚠️ Important Notes:
- Stateful: Security groups are stateful. If you allow inbound port 80, the response is automatically allowed out – you don’t need an outbound rule for responses.
- Default Deny Inbound: All inbound traffic is denied by default. You must explicitly allow what you need.
- Default Allow Outbound: All outbound traffic is allowed by default. You can restrict if needed.
- Security Group as Source: Best practice is to reference another security group ID as source (not IP addresses). This is more flexible.
- Multiple Security Groups: An instance can have up to 5 security groups. All rules are combined (union).
- VPC-Specific: Security groups are tied to a VPC. You can’t use a security group from one VPC in another VPC.
✅ Security Group Best Practices:
- Least Privilege: Only allow necessary ports and sources. Don’t use 0.0.0.0/0 unless absolutely necessary.
- Use Security Group IDs: Reference other security groups instead of IP addresses. More flexible and secure.
- Separate by Tier: Use different security groups for web, app, and database tiers.
- Add Descriptions: Always add descriptions to rules for documentation.
- Regular Review: Periodically review and remove unused rules.
- SSH Access: Restrict SSH (port 22) to your IP or bastion host security group, not 0.0.0.0/0.
8. Network ACLs (NACLs)
Security Groups vs Network ACLs
| Feature | Security Groups | Network ACLs |
|---|---|---|
| Level | Instance level | Subnet level |
| Stateful | Yes (return traffic allowed) | No (stateless – must allow return traffic) |
| Default | Deny all inbound, allow all outbound | Allow all (default NACL) or Deny all (custom NACL) |
| Rules | Allow only | Allow and deny |
| Evaluation | All rules evaluated (union) | Evaluated in order (first match wins) |
| Use Case | Primary security (recommended) | Additional layer, subnet-wide rules |
- Subnet-level security: When you need to apply rules to all resources in a subnet
- Explicit deny rules: When you need to explicitly block specific IPs or ports
- Compliance requirements: Some compliance standards require NACLs
- Defense in depth: Additional security layer beyond Security Groups
- NOT needed for: Most applications – Security Groups are usually sufficient
- You have a VPC and subnets created
- VPC automatically creates a default NACL (allows all traffic)
- NACL rules are evaluated in numerical order (rule number matters!)
- NACLs are stateless – you must allow both inbound AND outbound
- You can create custom NACLs or modify the default one
NACL Rule Evaluation – How It Works
- Numerical Order: Rules are evaluated from lowest rule number to highest
- First Match Wins: Processing stops at the first matching rule
- Explicit Deny: You can create deny rules (Security Groups can’t)
- Default NACL: Allows all traffic (created with VPC)
- Custom NACL: Denies all traffic by default (you must add allow rules)
- Stateless: Must explicitly allow return traffic (unlike Security Groups)
Method 1: Using AWS Console (GUI)
- Go to AWS Console → VPC Dashboard
- In left sidebar, click “Network ACLs”
- You’ll see the default NACL (created with VPC)
- Click “Create network ACL” button (top right)
- Name tag: Enter a name (e.g., “private-subnet-nacl”)
- VPC: Select your VPC
- Click “Create network ACL”
- Note: Custom NACLs deny all traffic by default
- Select your NACL → Click “Inbound rules” tab
- Click “Edit inbound rules” → “Add new rule”
- Rule #: Enter a number (e.g., 100). Lower numbers are evaluated first!
- Type: Select protocol (All traffic, TCP, UDP, ICMP, etc.)
- Protocol: Auto-filled based on type
- Port range: Enter port or range (e.g., 80, 443, 1024-65535)
- Source: Enter CIDR (e.g., 0.0.0.0/0, 10.0.0.0/16)
- Allow/Deny: Select Allow or Deny
- Click “Save changes”
- Important: Add rules in order – lower numbers first!
- Click “Outbound rules” tab
- Click “Edit outbound rules” → “Add new rule”
- Add rules similar to inbound (same structure)
- Critical: Since NACLs are stateless, you MUST allow return traffic!
- Example: If inbound allows port 80 from 0.0.0.0/0, outbound must allow ephemeral ports (1024-65535) to 0.0.0.0/0
- Click “Save changes”
- Select your NACL → Click “Subnet associations” tab
- Click “Edit subnet associations”
- Check the subnets you want to associate
- Click “Save associations”
- Note: Each subnet can only be associated with ONE NACL at a time
- Check inbound and outbound rules are correct
- Verify subnet associations
- Test connectivity from resources in associated subnets
Method 2: Using AWS CLI
# Replace vpc-xxx with your VPC ID
aws ec2 create-network-acl –vpc-id vpc-1234567890abcdef0 –tag-specifications ‘ResourceType=network-acl,Tags=[{Key=Name,Value=private-subnet-nacl}]’
# Note the NetworkAclId from output (e.g., acl-1234567890abcdef0)
# Step 2: Add Inbound Rule (Allow HTTP from Internet)
aws ec2 create-network-acl-entry \
–network-acl-id acl-1234567890abcdef0 \
–rule-number 100 \
–protocol tcp \
–port-range From=80,To=80 \
–cidr-block 0.0.0.0/0 \
–ingress \
–rule-action allow
# Step 3: Add Inbound Rule (Allow HTTPS from Internet)
aws ec2 create-network-acl-entry \
–network-acl-id acl-1234567890abcdef0 \
–rule-number 110 \
–protocol tcp \
–port-range From=443,To=443 \
–cidr-block 0.0.0.0/0 \
–ingress \
–rule-action allow
# Step 4: Add Outbound Rule (Allow Ephemeral Ports for Return Traffic)
aws ec2 create-network-acl-entry \
–network-acl-id acl-1234567890abcdef0 \
–rule-number 100 \
–protocol tcp \
–port-range From=1024,To=65535 \
–cidr-block 0.0.0.0/0 \
–egress \
–rule-action allow
# Step 5: Associate Subnet with NACL
# Replace subnet-xxx with your subnet ID
aws ec2 associate-network-acl \
–network-acl-id acl-1234567890abcdef0 \
–subnet-id subnet-1234567890abcdef0
Method 3: Using Terraform
resource “aws_network_acl” “private” {
vpc_id = aws_vpc.main.id
# Inbound: Allow HTTP from Internet
ingress {
rule_no = 100
protocol = “tcp”
from_port = 80
to_port = 80
cidr_block = “0.0.0.0/0”
action = “allow”
}
# Inbound: Allow HTTPS from Internet
ingress {
rule_no = 110
protocol = “tcp”
from_port = 443
to_port = 443
cidr_block = “0.0.0.0/0”
action = “allow”
}
# Inbound: Allow traffic from VPC
ingress {
rule_no = 120
protocol = “-1” # All protocols
from_port = 0
to_port = 65535
cidr_block = aws_vpc.main.cidr_block
action = “allow”
}
# Outbound: Allow Ephemeral Ports (for return traffic)
egress {
rule_no = 100
protocol = “tcp”
from_port = 1024
to_port = 65535
cidr_block = “0.0.0.0/0”
action = “allow”
}
# Outbound: Allow all traffic to VPC
egress {
rule_no = 110
protocol = “-1”
from_port = 0
to_port = 65535
cidr_block = aws_vpc.main.cidr_block
action = “allow”
}
tags = {
Name = “private-subnet-nacl”
}
}
# Associate Subnet with NACL
resource “aws_network_acl_association” “private” {
network_acl_id = aws_network_acl.private.id
subnet_id = aws_subnet.private.id
}
⚠️ Important Notes:
- Stateless: NACLs are stateless – you MUST allow both inbound AND outbound. If you allow inbound port 80, you must also allow outbound ephemeral ports (1024-65535) for return traffic.
- Rule Number Matters: Rules are evaluated in numerical order (lowest first). Use increments of 10 (100, 110, 120) to allow inserting rules later.
- First Match Wins: Processing stops at the first matching rule. Order your rules carefully!
- Default NACL: VPC creates a default NACL that allows all traffic. You can modify it or create custom ones.
- Custom NACL Default: Custom NACLs deny all traffic by default. You must explicitly add allow rules.
- Ephemeral Ports: For return traffic, allow ephemeral ports (1024-65535) in outbound rules.
- Subnet Association: Each subnet can only be associated with ONE NACL at a time.
✅ Example: Complete NACL Configuration
Inbound Rules (for Web Server Subnet):
| Rule # | Type | Port | Source | Action |
|---|---|---|---|---|
| 100 | HTTP | 80 | 0.0.0.0/0 | Allow |
| 110 | HTTPS | 443 | 0.0.0.0/0 | Allow |
| 120 | All Traffic | All | 10.0.0.0/16 | Allow |
| * | All Traffic | All | 0.0.0.0/0 | Deny |
Outbound Rules (for Return Traffic):
| Rule # | Type | Port | Destination | Action |
|---|---|---|---|---|
| 100 | TCP | 1024-65535 | 0.0.0.0/0 | Allow |
| 110 | All Traffic | All | 10.0.0.0/16 | Allow |
| * | All Traffic | All | 0.0.0.0/0 | Deny |
Note: Rule * (asterisk) is the default deny rule that catches everything not matched by previous rules.
9. VPC Peering
- Connect workloads in different VPCs (same account or cross-account)
- Access shared services VPC (logging, authentication, etc.)
- Hybrid architectures (dev/test in separate VPCs)
- Multi-region communication without Internet (cross-region peering)
- Use Transit Gateway instead if you need hub-and-spoke or transitive routing
- Both VPCs must have non-overlapping CIDR blocks (e.g., 10.0.0.0/16 and 192.168.0.0/16)
- You need VPC IDs (e.g., vpc-aaa for requester, vpc-bbb for accepter)
- Security groups and NACLs must allow traffic from peer VPC CIDR
- Route tables in each VPC must be updated to point to the peering connection
- Cross-account: accepter must accept the peering request
- Cross-region: pay data transfer charges (same-region peering is free)
Method 1: Using AWS Console (GUI)
- Go to VPC Dashboard → Peering Connections → Create peering connection
- Name tag: e.g.,
vpc-a-to-vpc-b - VPC (Requester): Select VPC A
- Account: Same account or another account
- Region: Choose same region or different region (cross-region)
- VPC (Accepter): Select VPC B (or enter VPC ID for other account)
- Click Create peering connection
- Go to Peering Connections
- Select the new peering connection (status: Pending acceptance)
- Click Actions → Accept request
- Cross-account: Accepter must switch to their account to accept
- Status becomes Active after acceptance
- In VPC A:
- Go to Route Tables → select the route table for subnets that need access
- Edit routes → Add route
- Destination: CIDR of VPC B (e.g., 192.168.0.0/16)
- Target: Select Peering Connection → choose your peering (pcx-…)
- Save changes
- In VPC B: Repeat same steps but with VPC A CIDR
- Key: Routes must be added on both sides, otherwise traffic is one-way!
- Allow traffic from the peer VPC CIDR
- Example: Database SG in VPC B → allow port 3306 from 10.0.0.0/16
- NACLs (if used) must also allow traffic from peer CIDR
- SSH into instance in VPC A → ping private IP of instance in VPC B
- Test application ports (e.g., curl database endpoint)
- Verify CloudWatch logs / VPC Flow Logs if troubleshooting
Method 2: Using AWS CLI
# Replace vpc-aaa (requester) and vpc-bbb (accepter)
aws ec2 create-vpc-peering-connection \
–vpc-id vpc-aaa111222333 \
–peer-vpc-id vpc-bbb444555666 \
–tag-specifications ‘ResourceType=vpc-peering-connection,Tags=[{Key=Name,Value=vpc-a-to-vpc-b}]’
# Note pcx-12345… from output
# Step 2: Accept Peering (Accepter Account)
aws ec2 accept-vpc-peering-connection –vpc-peering-connection-id pcx-1234567890abcdef0
# Step 3: Update Route Table in VPC A
aws ec2 create-route \
–route-table-id rtb-aaa111222333 \
–destination-cidr-block 192.168.0.0/16 \
–vpc-peering-connection-id pcx-1234567890abcdef0
# Step 4: Update Route Table in VPC B
aws ec2 create-route \
–route-table-id rtb-bbb444555666 \
–destination-cidr-block 10.0.0.0/16 \
–vpc-peering-connection-id pcx-1234567890abcdef0
Method 3: Using Terraform
resource “aws_vpc_peering_connection” “main” {
vpc_id = aws_vpc.vpc_a.id
peer_vpc_id = aws_vpc.vpc_b.id
auto_accept = true # false if cross-account (accepter must accept)
tags = {
Name = “vpc-a-to-vpc-b”
}
}
# Route in VPC A
resource “aws_route” “a_to_b” {
route_table_id = aws_route_table.a.id
destination_cidr_block = aws_vpc.vpc_b.cidr_block
vpc_peering_connection_id = aws_vpc_peering_connection.main.id
}
# Route in VPC B
resource “aws_route” “b_to_a” {
route_table_id = aws_route_table.b.id
destination_cidr_block = aws_vpc.vpc_a.cidr_block
vpc_peering_connection_id = aws_vpc_peering_connection.main.id
}
⚠️ Important Notes:
- Non-overlapping CIDRs: VPCs must have unique CIDR blocks (10.0.0.0/16 cannot peer with 10.0.1.0/24)
- No Transitive Routing: VPC A ↔ VPC B and VPC B ↔ VPC C does NOT mean VPC A ↔ VPC C. Use Transit Gateway for hub-and-spoke.
- Routes Required on BOTH sides: Without routes, traffic won’t flow (common troubleshooting issue).
- Security Groups / NACLs: Must allow traffic from peer VPC CIDR.
- Cross-Region: Supported, but data transfer charges apply.
- DNS Resolution: Enable DNS resolution from peer VPC if you need private hosted zone resolution across peers.
10. VPC Endpoints
- No Internet gateway or NAT required
- Traffic stays within AWS network (faster, more secure)
- No data transfer charges (for Gateway endpoints)
- Improved security (no Internet exposure)
- Lower latency
- Private subnets accessing AWS services: Resources in private subnets need to access S3, ECR, Secrets Manager, etc.
- Cost optimization: Avoid NAT Gateway costs ($0.045/hour) by using VPC Endpoints
- Security compliance: No Internet access required – all traffic stays private
- High data transfer: For S3/DynamoDB, Gateway Endpoints are free (no data charges)
- NOT needed if: Resources are in public subnets with Internet Gateway, or you don’t mind NAT Gateway costs
- You have a VPC and subnets created
- For Gateway Endpoint: Need route tables to add routes
- For Interface Endpoint: Need subnets and security groups
- Gateway Endpoints: Only for S3 and DynamoDB (free)
- Interface Endpoints: For most other AWS services (costs money)
Types of VPC Endpoints
| Type | Services | Routing | Cost | Security Group |
|---|---|---|---|---|
| Gateway Endpoint | S3, DynamoDB | Route table entry | Free | Not needed |
| Interface Endpoint | Most AWS services (ECR, Secrets Manager, API Gateway, etc.) | ENI in your subnet | $0.01/hour + data transfer | Required |
Method 1: Create Gateway Endpoint (S3/DynamoDB) – AWS Console
- Go to AWS Console → VPC Dashboard
- In left sidebar, click “Endpoints”
- Click “Create endpoint” button (top right)
- Name tag: Enter a name (e.g., “s3-gateway-endpoint”)
- Service category: Select “AWS services”
- Service name: Search and select “com.amazonaws.region.s3” (or DynamoDB)
- VPC: Select your VPC
- Route tables: Select route tables for subnets that need access (usually private subnets)
- Policy: Full access (or create custom policy)
- Click “Create endpoint”
- Endpoint is created immediately (no waiting)
- Check route tables – you’ll see a new route for S3/DynamoDB
Method 2: Create Interface Endpoint (ECR, Secrets Manager, etc.) – AWS Console
- Go to AWS Console → VPC Dashboard → Endpoints
- Click “Create endpoint”
- Name tag: Enter a name (e.g., “ecr-interface-endpoint”)
- Service category: Select “AWS services”
- Service name: Search and select service (e.g., “com.amazonaws.region.ecr.api”)
- VPC: Select your VPC
- Subnets: Select subnets (at least 2 in different AZs for HA)
- Security group: Select or create security group (must allow HTTPS from your resources)
- Enable Private DNS: Yes (recommended – makes it transparent)
- Click “Create endpoint”
- Wait 1-2 minutes for endpoint to become available
- Status will change from “Creating” to “Available”
- Note the DNS names (if private DNS disabled)
Method 3: Using AWS CLI
aws ec2 create-vpc-endpoint \
–vpc-id vpc-1234567890abcdef0 \
–service-name com.amazonaws.us-east-1.s3 \
–route-table-ids rtb-1234567890abcdef0 rtb-0987654321fedcba0 \
–tag-specifications ‘ResourceType=vpc-endpoint,Tags=[{Key=Name,Value=s3-gateway-endpoint}]’
# Create Interface Endpoint for ECR
aws ec2 create-vpc-endpoint \
–vpc-id vpc-1234567890abcdef0 \
–service-name com.amazonaws.us-east-1.ecr.api \
–vpc-endpoint-type Interface \
–subnet-ids subnet-1234567890abcdef0 subnet-0987654321fedcba0 \
–security-group-ids sg-1234567890abcdef0 \
–private-dns-enabled \
–tag-specifications ‘ResourceType=vpc-endpoint,Tags=[{Key=Name,Value=ecr-interface-endpoint}]’
Method 4: Using Terraform
resource “aws_vpc_endpoint” “s3” {
vpc_id = aws_vpc.main.id
service_name = “com.amazonaws.us-east-1.s3”
vpc_endpoint_type = “Gateway”
route_table_ids = [aws_route_table.private.id]
tags = {
Name = “s3-gateway-endpoint”
}
}
# Interface Endpoint for ECR (Costs money)
resource “aws_vpc_endpoint” “ecr” {
vpc_id = aws_vpc.main.id
service_name = “com.amazonaws.us-east-1.ecr.api”
vpc_endpoint_type = “Interface”
subnet_ids = [aws_subnet.private.id, aws_subnet.private2.id]
security_group_ids = [aws_security_group.vpc_endpoint.id]
private_dns_enabled = true
tags = {
Name = “ecr-interface-endpoint”
}
}
⚠️ Important Notes:
- Gateway Endpoints: Only for S3 and DynamoDB, free, added as route in route table, no security group needed
- Interface Endpoints: For most AWS services, costs $0.01/hour per endpoint + data transfer, creates ENI in subnet, requires security group
- Private DNS: Enable private DNS for Interface Endpoints to make it transparent (your code doesn’t need changes)
- High Availability: Create Interface Endpoints in at least 2 subnets (different AZs) for redundancy
- Security Groups: Interface Endpoint security group must allow HTTPS (port 443) from your resources
- Cost Consideration: Gateway Endpoints are free. Interface Endpoints cost money – calculate if it’s cheaper than NAT Gateway
- Region-Specific: Endpoints are region-specific. An endpoint in us-east-1 only accesses services in us-east-1
✅ Cost Comparison Example:
Scenario: Private subnet needs S3 and ECR access
- Option 1: NAT Gateway
- Cost: $0.045/hour (~$32/month) + $0.045/GB data transfer
- Works for all Internet and AWS services
- Option 2: VPC Endpoints
- S3 Gateway Endpoint: Free
- ECR Interface Endpoint: $0.01/hour (~$7/month) + $0.01/GB
- Total: ~$7/month (much cheaper if high S3 usage!)
- Best Practice: Use Gateway Endpoints for S3/DynamoDB (free), Interface Endpoints for frequently accessed services, NAT Gateway only if needed for Internet access
11. Transit Gateway
- Multiple VPCs: When you have 3+ VPCs that need to communicate (more efficient than many peering connections)
- Transitive routing needed: When VPC A needs to reach VPC C through VPC B (peering doesn’t support this)
- Centralized management: When you want one place to manage all network connectivity
- Hybrid cloud: When connecting VPCs with on-premises (VPN)
- Scalability: When you need to connect hundreds or thousands of VPCs
- NOT needed for: Just 2 VPCs (use VPC Peering – it’s free)
- You have multiple VPCs that need to communicate
- VPCs must have non-overlapping CIDR blocks
- Decide on routing strategy (shared services, dev/prod separation)
- Cost: $0.05/hour (~$36/month) + data transfer charges
- You can attach up to 5,000 VPCs to one Transit Gateway
Transit Gateway vs VPC Peering
| Feature | VPC Peering | Transit Gateway |
|---|---|---|
| Connections | One-to-one | Hub-and-spoke |
| Transitive | No | Yes |
| Scalability | Limited (few VPCs) | Thousands of VPCs |
| Cost | Free | $0.05/hour + data transfer |
| Use Case | 2-3 VPCs | Many VPCs, complex networks |
Method 1: Using AWS Console (GUI)
- Go to AWS Console → VPC Dashboard
- In left sidebar, click “Transit Gateways”
- Click “Create transit gateway” button
- Name tag: Enter a name (e.g., “main-transit-gateway”)
- Description: Optional description
- ASN: Leave default or enter custom (for BGP)
- DNS support: Enable (recommended)
- Click “Create transit gateway”
- Wait 1-2 minutes for creation
- Select your Transit Gateway → “Route tables” tab
- Click “Create transit gateway route table”
- Create separate route tables for different environments (e.g., dev, prod, shared)
- This allows you to control which VPCs can communicate
- Select Transit Gateway → “Transit gateway attachments” tab
- Click “Create transit gateway attachment”
- Attachment type: VPC
- VPC: Select VPC to attach
- Subnets: Select at least one subnet (preferably one per AZ)
- Route table: Select which route table to associate with
- Click “Create attachment”
- Repeat for each VPC you want to connect
- Select route table → “Propagations” tab
- Click “Create propagation”
- Select attachments to propagate routes from
- This automatically adds routes from attached VPCs
- Go to “Route Tables” in VPC Dashboard
- Select route table for each VPC subnet
- Click “Edit routes” → “Add route”
- Destination: CIDR of peer VPC (e.g., 10.1.0.0/16)
- Target: Select “Transit Gateway” → Choose your TGW attachment
- Click “Save changes”
- Repeat for routes to all peer VPCs
Method 2: Using AWS CLI
aws ec2 create-transit-gateway \
–description “Main Transit Gateway” \
–options DnsSupport=enable \
–tag-specifications ‘ResourceType=transit-gateway,Tags=[{Key=Name,Value=main-tgw}]’
# Note the TransitGatewayId from output (e.g., tgw-1234567890abcdef0)
# Step 2: Create VPC Attachment
aws ec2 create-transit-gateway-vpc-attachment \
–transit-gateway-id tgw-1234567890abcdef0 \
–vpc-id vpc-1234567890abcdef0 \
–subnet-ids subnet-1234567890abcdef0 subnet-0987654321fedcba0
# Step 3: Update VPC Route Table
aws ec2 create-route \
–route-table-id rtb-1234567890abcdef0 \
–destination-cidr-block 10.1.0.0/16 \
–transit-gateway-id tgw-1234567890abcdef0
Method 3: Using Terraform
resource “aws_ec2_transit_gateway” “main” {
description = “Main Transit Gateway”
dns_support = “enable”
tags = {
Name = “main-transit-gateway”
}
}
# Attach VPC A to Transit Gateway
resource “aws_ec2_transit_gateway_vpc_attachment” “vpc_a” {
subnet_ids = [aws_subnet.vpc_a_1.id, aws_subnet.vpc_a_2.id]
transit_gateway_id = aws_ec2_transit_gateway.main.id
vpc_id = aws_vpc.vpc_a.id
}
# Route in VPC A to VPC B via Transit Gateway
resource “aws_route” “vpc_a_to_vpc_b” {
route_table_id = aws_route_table.vpc_a.id
destination_cidr_block = aws_vpc.vpc_b.cidr_block
transit_gateway_id = aws_ec2_transit_gateway.main.id
}
⚠️ Important Notes:
- Route Tables: Transit Gateway has its own route tables. You can create multiple route tables for segmentation (dev, prod, shared services).
- Association vs Propagation: Association determines which attachments use a route table. Propagation automatically adds routes from attachments.
- VPC Route Tables: You must update VPC route tables to route traffic to Transit Gateway. Without routes, VPCs can’t communicate.
- Transitive Routing: Transit Gateway provides transitive routing – if VPC A and VPC B are both attached, and VPC C is also attached, all three can communicate.
- Cost: $0.05/hour (~$36/month) per Transit Gateway + data transfer charges ($0.02/GB same region, $0.02/GB cross-region).
- High Availability: Attach VPCs using subnets in multiple AZs for redundancy.
12. VPN Connections
- Hybrid cloud: When you need to connect on-premises data center to AWS VPC
- Secure connection: When you need encrypted communication over Internet
- Quick setup: When you need connectivity fast (VPN can be set up in minutes vs weeks for Direct Connect)
- Low to medium bandwidth: When your bandwidth needs are under 1.25 Gbps
- Cost-effective: When you want lower upfront costs (no physical installation)
- NOT needed for: VPC-to-VPC connectivity (use VPC Peering or Transit Gateway)
- You have a VPC created
- You have a compatible VPN device on-premises (router, firewall, or software VPN)
- Your on-premises network has a static public IP address (or dynamic with DDNS)
- You know your on-premises network CIDR block (e.g., 192.168.0.0/16)
- Your VPN device supports IPsec (IKEv1 or IKEv2)
- Cost: $0.05/hour per VPN connection (~$36/month) + data transfer charges
VPN Connection Components
- Virtual Private Gateway (VGW): VPN concentrator on AWS side, attached to VPC. Provides two public IP addresses for redundancy.
- Customer Gateway: Physical or software VPN device on your side. Represents your on-premises VPN endpoint.
- VPN Connection: The connection between VGW and Customer Gateway. Creates two IPsec tunnels for redundancy.
- VPN Tunnels: Two encrypted tunnels (Tunnel 1 and Tunnel 2) for high availability. If one fails, traffic uses the other.
VPN Connection Types
- Site-to-Site VPN: Connect entire networks (on-premises network ↔ VPC). This is what we’re covering here.
- AWS Client VPN: Connect individual users/devices to VPC. Different service, used for remote access.
Method 1: Using AWS Console (GUI)
- Go to AWS Console → VPC Dashboard
- In left sidebar, click “Virtual private gateways”
- Click “Create virtual private gateway” button
- Name tag: Enter a name (e.g., “main-vpg”)
- ASN: Leave default (64512) or enter custom BGP ASN
- Click “Create virtual private gateway”
- Select the VGW → Click “Actions” → “Attach to VPC”
- Select your VPC → Click “Attach”
- Wait for state to change from “detached” to “attached”
- In VPC Dashboard, click “Customer gateways” in left sidebar
- Click “Create customer gateway” button
- Name tag: Enter a name (e.g., “on-premises-cgw”)
- IP address: Enter your on-premises VPN device’s public IP address
- BGP ASN: Enter your on-premises BGP ASN (or leave default 65000 if not using BGP)
- Click “Create customer gateway”
- In VPC Dashboard, click “Site-to-Site VPN connections” in left sidebar
- Click “Create VPN connection” button
- Name tag: Enter a name (e.g., “on-premises-vpn”)
- Virtual private gateway: Select the VGW you created
- Customer gateway: Select the Customer Gateway you created
- Routing options: Choose “Static” or “Dynamic (BGP)”
- If Static: Enter your on-premises CIDR blocks (e.g., 192.168.0.0/16)
- Click “Create VPN connection”
- Wait 2-3 minutes for creation
- Select your VPN connection
- Click “Download configuration” button
- Select your VPN device vendor/model (e.g., Cisco ASA, pfSense, etc.)
- Download the configuration file
- This file contains IP addresses, pre-shared keys, and tunnel settings
- Use the downloaded configuration file
- Configure your VPN device with:
- – Virtual Private Gateway IP addresses (two IPs for redundancy)
- – Pre-shared keys (PSK) for each tunnel
- – IKE and IPsec settings
- – Route to VPC CIDR block
- Save and activate the configuration
- Go to “Route Tables” in VPC Dashboard
- Select route table for subnets that need on-premises access
- Click “Edit routes” → “Add route”
- Destination: Your on-premises CIDR (e.g., 192.168.0.0/16)
- Target: Select “Virtual private gateway” → Choose your VGW
- Click “Save changes”
- On your on-premises router/firewall, add route:
- – Destination: VPC CIDR (e.g., 10.0.0.0/16)
- – Next hop: VPN tunnel interface
- This allows on-premises devices to reach VPC resources
- In AWS Console, check VPN connection status
- Both tunnels should show “UP” status
- Test connectivity: Ping from on-premises to VPC instance (if ICMP allowed)
- Test connectivity: SSH/RDP from on-premises to VPC instance
- Check CloudWatch metrics for tunnel status
Method 2: Using AWS CLI
aws ec2 create-vpn-gateway \
–type ipsec.1 \
–tag-specifications ‘ResourceType=vpn-gateway,Tags=[{Key=Name,Value=main-vpg}]’
# Note the VpnGatewayId from output (e.g., vgw-1234567890abcdef0)
# Step 2: Attach VGW to VPC
aws ec2 attach-vpn-gateway \
–vpn-gateway-id vgw-1234567890abcdef0 \
–vpc-id vpc-1234567890abcdef0
# Step 3: Create Customer Gateway
aws ec2 create-customer-gateway \
–type ipsec.1 \
–public-ip 203.0.113.12 \
–bgp-asn 65000 \
–tag-specifications ‘ResourceType=customer-gateway,Tags=[{Key=Name,Value=on-premises-cgw}]’
# Note the CustomerGatewayId from output
# Step 4: Create VPN Connection (Static routing)
aws ec2 create-vpn-connection \
–type ipsec.1 \
–customer-gateway-id cgw-1234567890abcdef0 \
–vpn-gateway-id vgw-1234567890abcdef0 \
–options “StaticRoutesOnly=true” \
–tag-specifications ‘ResourceType=vpn-connection,Tags=[{Key=Name,Value=on-premises-vpn}]’
# Step 5: Add static routes to VPN connection
aws ec2 create-vpn-connection-route \
–vpn-connection-id vpn-1234567890abcdef0 \
–destination-cidr-block 192.168.0.0/16
Method 3: Using Terraform
resource “aws_vpn_gateway” “main” {
vpc_id = aws_vpc.main.id
tags = {
Name = “main-vpg”
}
}
# Create Customer Gateway
resource “aws_customer_gateway” “on_premises” {
bgp_asn = 65000
ip_address = “203.0.113.12”
type = “ipsec.1”
tags = {
Name = “on-premises-cgw”
}
}
# Create VPN Connection
resource “aws_vpn_connection” “on_premises” {
vpn_gateway_id = aws_vpn_gateway.main.id
customer_gateway_id = aws_customer_gateway.on_premises.id
type = “ipsec.1”
static_routes_only = true
tags = {
Name = “on-premises-vpn”
}
}
# Add static route
resource “aws_vpn_connection_route” “on_premises” {
destination_cidr_block = “192.168.0.0/16”
vpn_connection_id = aws_vpn_connection.on_premises.id
}
# Route in VPC to on-premises via VGW
resource “aws_route” “to_on_premises” {
route_table_id = aws_route_table.private.id
destination_cidr_block = “192.168.0.0/16”
gateway_id = aws_vpn_gateway.main.id
}
⚠️ Important Notes:
- Two Tunnels: AWS creates two IPsec tunnels for redundancy. Configure both on your VPN device. If one fails, the other continues working.
- Static vs Dynamic Routing: Static routing requires you to manually add routes. Dynamic (BGP) automatically exchanges routes but requires BGP support on both sides.
- Route Tables: You must add routes in VPC route tables pointing to VGW for on-premises CIDR blocks. Without routes, traffic won’t flow.
- Security Groups: Ensure Security Groups allow traffic from on-premises CIDR blocks. By default, Security Groups deny all inbound traffic.
- NACLs: Network ACLs must also allow traffic if you’re using them. They’re stateless, so allow both inbound and outbound.
- Cost: $0.05/hour per VPN connection (~$36/month) + data transfer charges. Data transfer over VPN is charged at Internet data transfer rates.
- Bandwidth: VPN supports up to 1.25 Gbps per tunnel.
- High Availability: Use two VPN connections to different VGWs in different AZs for maximum redundancy.
13. Best Practices
Network Design Best Practices
- Use Multiple AZs: Always deploy resources across multiple availability zones
- Separate Public and Private: Use public subnets for Internet-facing resources, private for internal
- CIDR Planning: Plan your CIDR blocks carefully to avoid conflicts
- NAT Gateway per AZ: Deploy NAT Gateway in each AZ for high availability
- Security Groups: Use security groups as primary security mechanism
- Least Privilege: Only open necessary ports and sources
- VPC Flow Logs: Enable VPC Flow Logs for monitoring and troubleshooting
- Use VPC Endpoints: Use VPC endpoints for AWS services to avoid NAT/Internet
Security Best Practices
- Private Subnets: Keep databases and application servers in private subnets
- Bastion Hosts: Use bastion hosts in public subnets for SSH access
- Security Group Rules: Reference security groups instead of IP addresses
- Regular Audits: Regularly review security group and NACL rules
- Encryption: Use encrypted connections (HTTPS, TLS) for sensitive data
- WAF: Use AWS WAF for application-level protection
Cost Optimization
- NAT Gateway: Use NAT Gateway only when needed (consider VPC endpoints)
- Data Transfer: Minimize cross-AZ data transfer
- VPC Endpoints: Use Gateway endpoints (free) for S3 and DynamoDB
- Elastic IPs: Release unused Elastic IPs to avoid charges
- Transit Gateway: Use Transit Gateway for multiple VPCs instead of many peering connections
14. Case Studies & Networking Playbooks
Two real-world scenarios with step-by-step networking setup. Click to expand the case study that matches what you’re building.
Case Study 1: Simple Web Application with EC2 Instances Toggle
Requirements
- Host a simple web application (e.g., WordPress, static website)
- 2 EC2 instances for high availability
- Internet access for users to reach the website
- Basic security (Security Groups)
- One region, one VPC
🏗️ Architecture Overview
AWS Architecture Diagram
💭 How to Think & Analyze
- Simple Setup: This is the most basic AWS networking setup—great for getting started
- Public Access: EC2 instances need public IPs so users can access them from the Internet
- Security: Use Security Groups to allow only HTTP (port 80) and HTTPS (port 443) traffic
- High Availability: Two instances in different Availability Zones ensure if one fails, the other continues
- CIDR Planning: Use 10.0.0.0/16 for VPC, 10.0.1.0/24 for public subnet (leaves room to grow)
🔍 Key Factors to Evaluate
- VPC: One VPC with CIDR 10.0.0.0/16
- Subnets: One public subnet (10.0.1.0/24) in us-east-1a, another (10.0.2.0/24) in us-east-1b
- Internet Gateway: Required for Internet access
- Route Table: Public route table with route to Internet Gateway (0.0.0.0/0 → igw-xxx)
- Security Groups: Allow inbound HTTP (80) and HTTPS (443) from Internet (0.0.0.0/0)
- EC2 Instances: Launch in public subnets with public IP addresses
✅ Preparation Checklist
- ✅ Choose a region (e.g., us-east-1)
- ✅ Plan VPC CIDR: 10.0.0.0/16
- ✅ Plan subnet CIDRs: 10.0.1.0/24 and 10.0.2.0/24
- ✅ Have an EC2 key pair ready for SSH access
- ✅ Know which ports your application needs (usually 80, 443)
🚀 Networking Implementation Steps
Step 1: Create VPC
- Go to AWS Console → VPC Dashboard
- Click “Create VPC” button
- Name tag: Enter “simple-web-vpc”
- IPv4 CIDR block: Enter “10.0.0.0/16”
- Leave other settings as default
- Click “Create VPC”
Step 2: Create Internet Gateway
- In VPC Dashboard, click “Internet Gateways” in left menu
- Click “Create internet gateway”
- Name tag: Enter “simple-web-igw”
- Click “Create internet gateway”
- Select the IGW you just created, click “Actions” → “Attach to VPC”
- Select your VPC (simple-web-vpc) and click “Attach internet gateway”
Step 3: Create Public Subnets
Create First Public Subnet:
- In VPC Dashboard, click “Subnets” → “Create subnet”
- VPC: Select “simple-web-vpc”
- Subnet name: Enter “public-subnet-1a”
- Availability Zone: Select “us-east-1a”
- IPv4 CIDR block: Enter “10.0.1.0/24”
- Click “Create subnet”
Create Second Public Subnet:
- Click “Create subnet” again
- VPC: Select “simple-web-vpc”
- Subnet name: Enter “public-subnet-1b”
- Availability Zone: Select “us-east-1b”
- IPv4 CIDR block: Enter “10.0.2.0/24”
- Click “Create subnet”
Step 4: Create Route Table
- In VPC Dashboard, click “Route Tables” → “Create route table”
- Name: Enter “public-route-table”
- VPC: Select “simple-web-vpc”
- Click “Create route table”
- Select the route table, go to “Routes” tab, click “Edit routes”
- Click “Add route”
- Destination: Enter “0.0.0.0/0”
- Target: Select “Internet Gateway” → choose your IGW
- Click “Save changes”
- Go to “Subnet associations” tab, click “Edit subnet associations”
- Select both public subnets (public-subnet-1a and public-subnet-1b)
- Click “Save associations”
Step 5: Create Security Group
- In VPC Dashboard, click “Security Groups” → “Create security group”
- Security group name: Enter “web-servers-sg”
- Description: Enter “Allow HTTP and HTTPS traffic”
- VPC: Select “simple-web-vpc”
- In “Inbound rules”, click “Add rule”:
- Type: HTTP
- Source: Anywhere-IPv4 (0.0.0.0/0)
- Click “Add rule” again:
- Type: HTTPS
- Source: Anywhere-IPv4 (0.0.0.0/0)
- Click “Add rule” again:
- Type: SSH
- Source: My IP (or enter your IP address)
- Leave outbound rules as default (allow all)
- Click “Create security group”
Step 6: Launch EC2 Instances
Launch First EC2 Instance:
- Go to EC2 Dashboard → “Launch Instance”
- Name: Enter “web-server-1”
- AMI: Select “Amazon Linux 2” (free tier eligible)
- Instance type: Select “t2.micro” (free tier)
- Key pair: Select or create a key pair for SSH access
- In “Network settings”:
- VPC: Select “simple-web-vpc”
- Subnet: Select “public-subnet-1a”
- Auto-assign Public IP: Enable
- Security group: Select “web-servers-sg”
- Click “Launch Instance”
Launch Second EC2 Instance:
- Click “Launch Instance” again
- Name: Enter “web-server-2”
- Use same AMI and instance type (t2.micro)
- Same key pair
- In “Network settings”:
- VPC: Select “simple-web-vpc”
- Subnet: Select “public-subnet-1b” (different AZ!)
- Auto-assign Public IP: Enable
- Security group: Select “web-servers-sg”
- Click “Launch Instance”
✅ Done! You now have 2 web servers in different Availability Zones. Test by accessing their public IP addresses in a browser.
Case Study 2: Web App with Private Database (Beginner to Intermediate) Toggle
Requirements
- Web application accessible from Internet
- Database that should NOT be accessible from Internet
- Web servers need to access database privately
- 2 Availability Zones for high availability
- Use NAT Gateway for web servers to download updates
🏗️ Architecture Overview
AWS Architecture Diagram
💭 How to Think & Analyze
- Two-Tier Architecture: Web tier (public) and Database tier (private)
- Security: Database in private subnet means no direct Internet access – much more secure!
- NAT Gateway: Allows web servers to download updates/patches from Internet, but Internet cannot reach them directly
- Traffic Flow: Internet → Public Subnet (Web) → Private Subnet (Database)
- High Availability: Use 2 AZs – one public + one private subnet per AZ
🔍 Key Factors to Evaluate
- VPC: 10.0.0.0/16
- Public Subnets: 10.0.1.0/24 (AZ-A), 10.0.2.0/24 (AZ-B) – for web servers and NAT Gateway
- Private Subnets: 10.0.10.0/24 (AZ-A), 10.0.11.0/24 (AZ-B) – for database
- Internet Gateway: Attached to VPC for public access
- NAT Gateway: One per AZ in public subnet (for web servers to access Internet)
- Route Tables: Public RT → IGW, Private RT → NAT Gateway
- Security Groups: Web SG allows 80/443 from Internet, DB SG allows 3306/5432 from Web SG only
✅ Preparation Checklist
- ✅ Choose region and 2 Availability Zones
- ✅ Plan CIDR blocks: VPC (10.0.0.0/16), Public (10.0.1.0/24, 10.0.2.0/24), Private (10.0.10.0/24, 10.0.11.0/24)
- ✅ Decide database type (MySQL or PostgreSQL)
- ✅ Have database credentials ready (or use Secrets Manager)
- ✅ Allocate Elastic IP for NAT Gateway
🚀 Networking Implementation Steps
Step 1: Create VPC and Internet Gateway
Create VPC:
- Go to VPC Dashboard → “Create VPC”
- Name: Enter “web-db-vpc”
- IPv4 CIDR: Enter “10.0.0.0/16”
- Click “Create VPC”
Create Internet Gateway:
- Click “Internet Gateways” → “Create internet gateway”
- Name: Enter “web-db-igw”
- Click “Create internet gateway”
- Select it, click “Actions” → “Attach to VPC”
- Select “web-db-vpc” and click “Attach internet gateway”
Step 2: Create Public and Private Subnets
Create Public Subnet in AZ-A:
- Click “Subnets” → “Create subnet”
- VPC: Select “web-db-vpc”
- Name: Enter “public-subnet-a”
- AZ: Select “us-east-1a”
- CIDR: Enter “10.0.1.0/24”
- Click “Create subnet”
Create Public Subnet in AZ-B:
- Click “Create subnet”
- Name: Enter “public-subnet-b”
- AZ: Select “us-east-1b”
- CIDR: Enter “10.0.2.0/24”
- Click “Create subnet”
Create Private Subnet in AZ-A:
- Click “Create subnet”
- Name: Enter “private-subnet-a”
- AZ: Select “us-east-1a”
- CIDR: Enter “10.0.10.0/24”
- Click “Create subnet”
Create Private Subnet in AZ-B:
- Click “Create subnet”
- Name: Enter “private-subnet-b”
- AZ: Select “us-east-1b”
- CIDR: Enter “10.0.11.0/24”
- Click “Create subnet”
Step 3: Create NAT Gateway
Allocate Elastic IP:
- Go to EC2 Dashboard → “Elastic IPs”
- Click “Allocate Elastic IP address”
- Click “Allocate” (leave defaults)
Create NAT Gateway:
- Go to VPC Dashboard → “NAT Gateways”
- Click “Create NAT gateway”
- Name: Enter “main-nat-gateway”
- Subnet: Select “public-subnet-a”
- Elastic IP: Select the Elastic IP you just created
- Click “Create NAT gateway”
- ⚠️ Wait 2-3 minutes for NAT Gateway to become available
Step 4: Create Route Tables
Create Public Route Table:
- Click “Route Tables” → “Create route table”
- Name: Enter “public-route-table”
- VPC: Select “web-db-vpc”
- Click “Create route table”
- Select it, go to “Routes” tab → “Edit routes”
- Click “Add route”, enter:
- Destination: 0.0.0.0/0
- Target: Internet Gateway → select your IGW
- Click “Save changes”
- Go to “Subnet associations” → “Edit subnet associations”
- Select both public subnets, click “Save associations”
Create Private Route Table:
- Click “Create route table”
- Name: Enter “private-route-table”
- VPC: Select “web-db-vpc”
- Click “Create route table”
- Select it, go to “Routes” tab → “Edit routes”
- Click “Add route”, enter:
- Destination: 0.0.0.0/0
- Target: NAT Gateway → select your NAT Gateway
- Click “Save changes”
- Go to “Subnet associations” → “Edit subnet associations”
- Select both private subnets, click “Save associations”
Step 5: Create Security Groups
Create Web Security Group:
- Click “Security Groups” → “Create security group”
- Name: Enter “web-servers-sg”
- Description: Enter “Allow HTTP/HTTPS from Internet”
- VPC: Select “web-db-vpc”
- In “Inbound rules”, add:
- HTTP from 0.0.0.0/0
- HTTPS from 0.0.0.0/0
- Click “Create security group”
Create Database Security Group:
- Click “Create security group”
- Name: Enter “database-sg”
- Description: Enter “Allow MySQL from web servers”
- VPC: Select “web-db-vpc”
- In “Inbound rules”, add:
- Type: MySQL/Aurora (port 3306)
- Source: Custom → Select “web-servers-sg” security group
- Click “Create security group”
Step 6: Create RDS Subnet Group and Database
Create DB Subnet Group:
- Go to RDS Dashboard → “Subnet groups”
- Click “Create DB subnet group”
- Name: Enter “main-db-subnet-group”
- Description: Enter “Subnet group for web app database”
- VPC: Select “web-db-vpc”
- Availability Zones: Select “us-east-1a” and “us-east-1b”
- Subnets: Select both private subnets (private-subnet-a and private-subnet-b)
- Click “Create”
Create RDS Database:
- Go to RDS Dashboard → “Databases” → “Create database”
- Engine: Select “MySQL” or “PostgreSQL”
- Template: Select “Free tier” (for learning)
- DB instance identifier: Enter “webapp-db”
- Master username: Enter “admin”
- Master password: Enter a strong password (save it!)
- DB instance class: Select “db.t3.micro” (free tier)
- Storage: Leave defaults (20 GB)
- In “Connectivity” section:
- VPC: Select “web-db-vpc”
- Subnet group: Select “main-db-subnet-group”
- Public access: Select “No” (important! Database stays private)
- VPC security group: Select “database-sg”
- Click “Create database”
- ⏳ Wait 5-10 minutes for database to be created
✅ Done! Your database is now in a private subnet and can only be accessed by web servers using the database security group.
15. Technical Q&A for DevOps & Cloud Engineers
Complete Terraform Guide
Learn more about Rails
Learn more about Mern Stack
Learn more about DevOps


