AWS VPC: Complete Networking Guide

Amazon Virtual Private Cloud (VPC) is the networking layer of AWS. Every EC2 instance, Lambda function, and RDS database lives inside a VPC. Understanding VPC architecture is essential for building secure, scalable AWS applications.
VPC Core Concepts
| Component | Purpose | Key Details |
|---|---|---|
| VPC | Isolated network environment | CIDR block: /16 to /28 |
| Subnet | Network segment within VPC | Public or private, spans one AZ |
| Internet Gateway | Connect VPC to internet | One per VPC, horizontally scaled |
| NAT Gateway | Outbound internet for private subnets | Managed, per-AZ, billable |
| Route Table | Traffic routing rules | Associated with subnets |
| Security Group | Instance-level firewall | Stateful, allow rules only |
| NACL | Subnet-level firewall | Stateless, allow and deny rules |
VPC Architecture with Terraform
Here’s a production-ready VPC setup with public and private subnets:
# VPC with public and private subnets
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
enable_dns_hostnames = true
enable_dns_support = true
tags = {
Name = "production-vpc"
}
}
# Internet Gateway
resource "aws_internet_gateway" "main" {
vpc_id = aws_vpc.main.id
tags = { Name = "production-igw" }
}
# Public subnets (one per AZ)
resource "aws_subnet" "public" {
count = 3
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(aws_vpc.main.cidr_block, 4, count.index)
availability_zone = data.aws_availability_zones.available.names[count.index]
map_public_ip_on_launch = true
tags = {
Name = "public-${count.index + 1}"
Tier = "public"
}
}
# Private subnets (one per AZ)
resource "aws_subnet" "private" {
count = 3
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(aws_vpc.main.cidr_block, 4, count.index + 3)
availability_zone = data.aws_availability_zones.available.names[count.index]
tags = {
Name = "private-${count.index + 1}"
Tier = "private"
}
}
# NAT Gateway (one per AZ for HA)
resource "aws_eip" "nat" {
count = 3
domain = "vpc"
}
resource "aws_nat_gateway" "main" {
count = 3
allocation_id = aws_eip.nat[count.index].id
subnet_id = aws_subnet.public[count.index].id
tags = { Name = "nat-${count.index + 1}" }
}
Security Groups vs NACLs
| Feature | Security Group | NACL |
|---|---|---|
| Scope | Instance (ENI) | Subnet |
| Statefulness | Stateful (return traffic auto-allowed) | Stateless (must allow both directions) |
| Rule Types | Allow only | Allow and Deny |
| Evaluation | All rules evaluated | Rules evaluated in order |
| Default | Deny all inbound, allow all outbound | Allow all (default NACL) |
Security Group Examples
# Web tier security group
resource "aws_security_group" "web" {
name = "web-tier"
description = "Allow HTTP/HTTPS from anywhere"
vpc_id = aws_vpc.main.id
ingress {
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
# App tier - only allow from web tier
resource "aws_security_group" "app" {
name = "app-tier"
description = "Allow from web tier only"
vpc_id = aws_vpc.main.id
ingress {
from_port = 8080
to_port = 8080
protocol = "tcp"
security_groups = [aws_security_group.web.id]
}
}
# Database tier - only allow from app tier
resource "aws_security_group" "db" {
name = "db-tier"
vpc_id = aws_vpc.main.id
ingress {
from_port = 5432
to_port = 5432
protocol = "tcp"
security_groups = [aws_security_group.app.id]
}
}
VPC Peering and Transit Gateway
When to Use Each:
- VPC Peering: Simple 1-to-1 connections, low cost, up to ~125 connections per VPC
- Transit Gateway: Hub-and-spoke, 5000+ attachments, centralized routing, higher cost
# VPC Peering Connection
resource "aws_vpc_peering_connection" "peer" {
peer_vpc_id = aws_vpc.prod.id
vpc_id = aws_vpc.dev.id
auto_accept = true
tags = { Name = "dev-to-prod" }
}
# Add routes for peering
resource "aws_route" "dev_to_prod" {
route_table_id = aws_route_table.dev.id
destination_cidr_block = aws_vpc.prod.cidr_block
vpc_peering_connection_id = aws_vpc_peering_connection.peer.id
}
# Transit Gateway for hub-and-spoke
resource "aws_ec2_transit_gateway" "main" {
description = "Central transit gateway"
default_route_table_association = "disable"
default_route_table_propagation = "disable"
tags = { Name = "central-tgw" }
}
VPC Endpoints
Access AWS services privately without internet gateway:
# Gateway endpoint for S3 (free)
resource "aws_vpc_endpoint" "s3" {
vpc_id = aws_vpc.main.id
service_name = "com.amazonaws.us-east-1.s3"
route_table_ids = [
aws_route_table.private.id
]
tags = { Name = "s3-endpoint" }
}
# Interface endpoint for SSM (billed)
resource "aws_vpc_endpoint" "ssm" {
vpc_id = aws_vpc.main.id
service_name = "com.amazonaws.us-east-1.ssm"
vpc_endpoint_type = "Interface"
private_dns_enabled = true
subnet_ids = aws_subnet.private[*].id
security_group_ids = [aws_security_group.vpce.id]
}
VPC Flow Logs
Monitor network traffic for security and troubleshooting:
# Flow logs to CloudWatch
resource "aws_flow_log" "main" {
vpc_id = aws_vpc.main.id
traffic_type = "ALL"
iam_role_arn = aws_iam_role.flow_log.arn
log_destination = aws_cloudwatch_log_group.flow_log.arn
tags = { Name = "vpc-flow-logs" }
}
# Flow logs to S3 (cheaper for long-term)
resource "aws_flow_log" "s3" {
vpc_id = aws_vpc.main.id
traffic_type = "ALL"
log_destination_type = "s3"
log_destination = aws_s3_bucket.flow_logs.arn
max_aggregation_interval = 60
}
CIDR Planning Guide
| CIDR | IPs Available | Use Case |
|---|---|---|
| /16 | 65,536 | Large VPC, room to grow |
| /20 | 4,096 | Medium workload |
| /24 | 256 | Typical subnet size |
| /28 | 16 | Smallest subnet allowed |
CIDR Planning Tips:
- AWS reserves 5 IPs per subnet (first 4 + last)
- Don’t overlap with on-premises networks if using VPN/Direct Connect
- Leave room for future VPCs (avoid using all of 10.0.0.0/8)
- Use RFC 1918 private ranges: 10.x, 172.16-31.x, 192.168.x
Best Practices
- Multi-AZ Design: Deploy subnets across at least 2 AZs for high availability
- Use Private Subnets: Keep databases and app servers in private subnets
- VPC Endpoints: Use endpoints for AWS services to reduce NAT costs
- Flow Logs: Enable VPC Flow Logs for security monitoring
- Security Groups: Reference other security groups instead of CIDR blocks
- Plan CIDR Ranges: Avoid overlapping with other VPCs or on-premises
Further Reading
Pro Tip: Use AWS VPC Reachability Analyzer to troubleshoot connectivity issues. It shows the path packets take and where they’re blocked.