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.
Stay in the loop
Get the latest wildlife research and conservation news delivered to your inbox.