Amazon Virtual Private Cloud (VPC) is your isolated network within AWS. Every EC2 instance, RDS database, and Lambda function in a VPC operates inside this software-defined network. Proper VPC design is the backbone of secure, scalable cloud architecture.

VPC Fundamentals

  ┌─────────────────────────────────────────────────────────┐
│  VPC (10.0.0.0/16)                                      │
│  ┌─────────────────────┐  ┌─────────────────────┐      │
│  │ Public Subnet AZ-a  │  │ Public Subnet AZ-b  │      │
│  │ 10.0.1.0/24         │  │ 10.0.2.0/24         │      │
│  │ [ALB] [NAT GW]      │  │ [ALB] [NAT GW]      │      │
│  └─────────────────────┘  └─────────────────────┘      │
│  ┌─────────────────────┐  ┌─────────────────────┐      │
│  │ Private Subnet AZ-a │  │ Private Subnet AZ-b │      │
│  │ 10.0.10.0/24        │  │ 10.0.11.0/24        │      │
│  │ [App EC2] [RDS]     │  │ [App EC2] [RDS]     │      │
│  └─────────────────────┘  └─────────────────────┘      │
│         Internet Gateway ← Public subnets only           │
└─────────────────────────────────────────────────────────┘
  
Component Purpose
VPC Isolated network with CIDR block (e.g., 10.0.0.0/16)
Subnet Segment of VPC CIDR in one AZ
Internet Gateway (IGW) Allows public subnet resources to reach internet
NAT Gateway Outbound internet for private subnets
Route Table Directs traffic (local, IGW, NAT, VPC peering)
Security Group Stateful instance-level firewall
NACL Stateless subnet-level firewall

Create a VPC (CLI)

  # Create VPC
aws ec2 create-vpc --cidr-block 10.0.0.0/16 \
    --tag-specifications 'ResourceType=vpc,Tags=[{Key=Name,Value=production-vpc}]'

# Create subnets in two AZs
aws ec2 create-subnet --vpc-id vpc-xxx --cidr-block 10.0.1.0/24 \
    --availability-zone us-east-1a --tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=public-1a}]'
aws ec2 create-subnet --vpc-id vpc-xxx --cidr-block 10.0.2.0/24 \
    --availability-zone us-east-1b --tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=public-1b}]'
aws ec2 create-subnet --vpc-id vpc-xxx --cidr-block 10.0.10.0/24 \
    --availability-zone us-east-1a --tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=private-1a}]'
aws ec2 create-subnet --vpc-id vpc-xxx --cidr-block 10.0.11.0/24 \
    --availability-zone us-east-1b --tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=private-1b}]'

# Internet Gateway
aws ec2 create-internet-gateway --tag-specifications 'ResourceType=internet-gateway,Tags=[{Key=Name,Value=prod-igw}]'
aws ec2 attach-internet-gateway --internet-gateway-id igw-xxx --vpc-id vpc-xxx
  

Route Tables

  # Public route table: 0.0.0.0/0 → IGW
aws ec2 create-route-table --vpc-id vpc-xxx
aws ec2 create-route --route-table-id rtb-public --destination-cidr-block 0.0.0.0/0 \
    --gateway-id igw-xxx
aws ec2 associate-route-table --route-table-id rtb-public --subnet-id subnet-public-1a

# NAT Gateway in public subnet (requires Elastic IP)
aws ec2 allocate-address --domain vpc
aws ec2 create-nat-gateway --subnet-id subnet-public-1a --allocation-id eipalloc-xxx

# Private route table: 0.0.0.0/0 → NAT GW
aws ec2 create-route --route-table-id rtb-private --destination-cidr-block 0.0.0.0/0 \
    --nat-gateway-id nat-xxx
  

Cost note: NAT Gateway charges ~$0.045/hour + data processing. For dev, consider NAT Instance or VPC endpoints to reduce costs.

Security Groups vs NACLs

Feature Security Group NACL
Level Instance/ENI Subnet
State Stateful (return traffic auto-allowed) Stateless (must allow both directions)
Rules Allow only Allow and Deny
Default Deny all inbound Allow all
Use case Primary access control Additional subnet-level deny rules
  # App server SG — allow HTTP from ALB SG only
aws ec2 authorize-security-group-ingress \
    --group-id sg-app \
    --protocol tcp --port 8080 \
    --source-group sg-alb

# Database SG — allow PostgreSQL from app SG only
aws ec2 authorize-security-group-ingress \
    --group-id sg-database \
    --protocol tcp --port 5432 \
    --source-group sg-app
  

VPC Endpoints (Private AWS Access)

Avoid NAT Gateway costs for AWS service traffic:

  # Gateway endpoint for S3 (free)
aws ec2 create-vpc-endpoint \
    --vpc-id vpc-xxx \
    --service-name com.amazonaws.us-east-1.s3 \
    --route-table-ids rtb-private

# Interface endpoint for Secrets Manager (~$0.01/hour per AZ)
aws ec2 create-vpc-endpoint \
    --vpc-id vpc-xxx \
    --service-name com.amazonaws.us-east-1.secretsmanager \
    --vpc-endpoint-type Interface \
    --subnet-ids subnet-private-1a subnet-private-1b \
    --security-group-ids sg-endpoints
  

VPC Peering and Transit Gateway

Option Use Case Limitation
VPC Peering Two VPCs, full mesh small No transitive routing
Transit Gateway Hub-and-spoke, many VPCs Additional cost
VPN / Direct Connect Hybrid cloud (on-premises) Setup complexity
  # VPC Peering
aws ec2 create-vpc-peering-connection \
    --vpc-id vpc-prod --peer-vpc-id vpc-shared-services
aws ec2 accept-vpc-peering-connection --vpc-peering-connection-id pcx-xxx

# Add route in both VPCs
aws ec2 create-route --route-table-id rtb-prod \
    --destination-cidr-block 10.1.0.0/16 --vpc-peering-connection-id pcx-xxx
  

CIDR Planning Best Practices

VPC Size CIDR Usable IPs Recommendation
Small /24 256 Dev/test only
Medium /20 4,096 Single app production
Large /16 65,536 Multi-app, room to grow
Enterprise /12 1M+ Multi-region, many services

Reserve space for:

  • Public subnets: /24 per AZ
  • Private app subnets: /24 per AZ
  • Private DB subnets: /24 per AZ
  • Future expansion and peering (non-overlapping CIDRs)

Real-World Scenario: Production VPC

Subnet CIDR AZ Resources
public-web-1a 10.0.1.0/24 us-east-1a ALB, NAT GW
public-web-1b 10.0.2.0/24 us-east-1b ALB, NAT GW
private-app-1a 10.0.10.0/24 us-east-1a EC2 ASG, ECS
private-app-1b 10.0.11.0/24 us-east-1b EC2 ASG, ECS
private-db-1a 10.0.20.0/24 us-east-1a RDS primary
private-db-1b 10.0.21.0/24 us-east-1b RDS standby

Common Mistakes

  1. Single AZ deployment — always span at least two AZs for production
  2. Overlapping CIDRs — plan before peering or connecting to on-premises
  3. One NAT Gateway — single point of failure; use one per AZ in production
  4. Public RDS/database subnets — databases belong in private subnets
  5. Default VPC for production — create purpose-built VPCs with proper segmentation
  6. Ignoring VPC Flow Logs — enable for security monitoring and troubleshooting

Troubleshooting

Issue Check Fix
Instance can’t reach internet Route table, IGW, public IP Verify 0.0.0.0/0 → IGW on public subnet
Private instance no outbound NAT GW route, NAT in public subnet Add 0.0.0.0/0 → NAT in private route table
Can’t connect to RDS SG, subnet group, NACL SG must allow source on DB port
VPC peering no traffic Routes in both VPCs, SG Add peering routes; check SG allows peer CIDR
High NAT costs All AWS API via NAT Add VPC endpoints for S3, DynamoDB, etc.

Enable VPC Flow Logs

  aws ec2 create-flow-logs \
    --resource-type VPC \
    --resource-ids vpc-xxx \
    --traffic-type ALL \
    --log-destination-type cloud-watch-logs \
    --log-group-name /aws/vpc/flowlogs/production
  

Best Practices Summary

  • Use private subnets for application and database tiers
  • Deploy NAT Gateway per AZ for production HA
  • Implement VPC endpoints to reduce NAT costs and improve security
  • Apply least-privilege security groups — reference SG IDs, not CIDRs where possible
  • Enable VPC Flow Logs and send to CloudWatch or S3
  • Plan non-overlapping CIDR blocks before multi-VPC architecture
  • Use AWS Network Firewall or WAF for advanced threat protection

Next: Lambda — Serverless.