AWS VPC: Complete Networking and Security Guide

AWS VPC: Complete Networking Guide

Development team collaboration
Development team collaboration

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.

Jennifer Walsh

Jennifer Walsh

Author & Expert

Senior Cloud Solutions Architect with 12 years of experience in AWS, Azure, and GCP. Jennifer has led enterprise migrations for Fortune 500 companies and holds AWS Solutions Architect Professional and DevOps Engineer certifications. She specializes in serverless architectures, container orchestration, and cloud cost optimization. Previously a senior engineer at AWS Professional Services.

156 Articles
View All Posts