AWS VPC Design Patterns and Best Practices: Complete Guide 2024
Master AWS VPC design with comprehensive patterns for network segmentation, multi-AZ architecture, connectivity, security, and monitoring. Learn CIDR planning, NAT design, VPC endpoints, Transit Gateway, peering, and hybrid connectivity with 8+ production-ready code examples.
Amazon VPC (Virtual Private Cloud) forms the foundation of your AWS infrastructure, providing a secure, isolated network environment for your resources. Designing a VPC correctly is critical for building scalable, reliable, and secure applications. This comprehensive guide covers essential VPC design patterns, architectural best practices, and production-ready implementation strategies that will help you build enterprise-grade network infrastructure on AWS.
VPC Fundamentals and Design Considerations
Understanding VPC Architecture
A VPC is a logically isolated network environment within AWS where you can launch and manage AWS resources. Each VPC operates independently, giving you complete control over IP address space, subnets, route tables, gateways, and network access controls. Designing your VPC requires careful consideration of current requirements and future growth.
CIDR Planning: The Foundation of Scalable Networks
CIDR (Classless Inter-Domain Routing) planning is the first critical step in VPC design. Choosing the right CIDR block ensures you have sufficient IP addresses for your workloads while avoiding conflicts with other networks you may want to connect.
IP Address Range Selection
AWS allows three private IP ranges for VPCs:
- 10.0.0.0/8 (16,777,216 addresses) - Most common choice for large organizations
- 172.16.0.0/12 (1,048,576 addresses) - Mid-sized deployments
- 192.168.0.0/16 (65,536 addresses) - Smaller deployments or dev/test environments
When selecting your VPC CIDR, consider:
- Current Requirements: How many resources do you need today?
- Future Growth: Plan for 2-3 years of expansion
- VPC Peering and Hybrid Connectivity: Ensure no overlaps with on-premises networks or other VPCs
- Subnet Flexibility: Reserve space for multiple subnets across availability zones
Example CIDR Planning Strategy
Organization: TechCorp
Total Planning Horizon: 3 years
Requirement: Multi-region, multi-environment
VPC Allocation:
├── us-east-1 Production VPC: 10.0.0.0/16
│ ├── Public Subnets: 10.0.0.0/24 to 10.0.2.0/24 (3 AZs)
│ ├── Private Subnets: 10.0.10.0/24 to 10.0.12.0/24 (3 AZs)
│ └── Database Subnets: 10.0.20.0/24 to 10.0.22.0/24 (3 AZs)
├── us-east-1 Staging VPC: 10.1.0.0/16
├── us-west-2 Production VPC: 10.2.0.0/16
└── us-west-2 Staging VPC: 10.3.0.0/16
Reserved for on-premises: 172.16.0.0/12
This allocation gives you plenty of room for growth while maintaining non-overlapping networks.
Multi-AZ Architecture Patterns
High Availability Design Principles
Building a highly available VPC means distributing your resources across multiple Availability Zones (AZs). Each AZ has independent power, cooling, and networking infrastructure, so distributing resources across AZs ensures your application survives even if an entire data center fails.
Three-Tier Subnet Architecture
The standard pattern for production VPCs uses three subnet tiers:
-
Public Subnets - Direct internet access via Internet Gateway
- Purpose: Load balancers, NAT gateways, bastion hosts
- Route to IGW: 0.0.0.0/0 → Internet Gateway
- Auto-assign public IPs: Enabled
-
Private Subnets - Application tier with no direct internet access
- Purpose: Application servers, containers, microservices
- Route to internet: Via NAT Gateway (outbound only)
- Auto-assign public IPs: Disabled
-
Database Subnets - Isolated from internet
- Purpose: Databases, data caches, internal services
- No direct internet route
- Often isolated with Network ACLs
- Grouped in DB subnet groups for RDS
Terraform Configuration: Complete Multi-AZ VPC
Here's a production-ready Terraform configuration that implements the three-tier architecture:
# variables.tf
variable "vpc_cidr" {
default = "10.0.0.0/16"
description = "CIDR block for VPC"
}
variable "availability_zones" {
type = list(string)
default = ["a", "b", "c"]
}
variable "environment" {
default = "production"
}
# vpc.tf
resource "aws_vpc" "main" {
cidr_block = var.vpc_cidr
enable_dns_hostnames = true
enable_dns_support = true
tags = {
Name = "${var.environment}-vpc"
Environment = var.environment
}
}
# Internet Gateway
resource "aws_internet_gateway" "main" {
vpc_id = aws_vpc.main.id
tags = {
Name = "${var.environment}-igw"
}
}
# Public Subnets (one per AZ)
resource "aws_subnet" "public" {
count = length(var.availability_zones)
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(var.vpc_cidr, 8, count.index)
availability_zone = "${data.aws_availability_zones.available.names[count.index]}"
map_public_ip_on_launch = true
tags = {
Name = "${var.environment}-public-${var.availability_zones[count.index]}"
Type = "public"
}
}
# Private Subnets (one per AZ)
resource "aws_subnet" "private" {
count = length(var.availability_zones)
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(var.vpc_cidr, 8, count.index + 10)
availability_zone = "${data.aws_availability_zones.available.names[count.index]}"
tags = {
Name = "${var.environment}-private-${var.availability_zones[count.index]}"
Type = "private"
}
}
# Database Subnets (one per AZ)
resource "aws_subnet" "database" {
count = length(var.availability_zones)
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(var.vpc_cidr, 8, count.index + 20)
availability_zone = "${data.aws_availability_zones.available.names[count.index]}"
tags = {
Name = "${var.environment}-database-${var.availability_zones[count.index]}"
Type = "database"
}
}
# Elastic IPs for NAT Gateways (one per AZ for HA)
resource "aws_eip" "nat" {
count = length(var.availability_zones)
domain = "vpc"
tags = {
Name = "${var.environment}-nat-eip-${var.availability_zones[count.index]}"
}
depends_on = [aws_internet_gateway.main]
}
# NAT Gateways (one per AZ)
resource "aws_nat_gateway" "main" {
count = length(var.availability_zones)
allocation_id = aws_eip.nat[count.index].id
subnet_id = aws_subnet.public[count.index].id
tags = {
Name = "${var.environment}-nat-${var.availability_zones[count.index]}"
}
depends_on = [aws_internet_gateway.main]
}
# Route Tables
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 = "${var.environment}-public-rt"
}
}
resource "aws_route_table_association" "public" {
count = length(var.availability_zones)
subnet_id = aws_subnet.public[count.index].id
route_table_id = aws_route_table.public.id
}
# Private Route Tables (one per AZ, each with its own NAT Gateway)
resource "aws_route_table" "private" {
count = length(var.availability_zones)
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
nat_gateway_id = aws_nat_gateway.main[count.index].id
}
tags = {
Name = "${var.environment}-private-rt-${var.availability_zones[count.index]}"
}
}
resource "aws_route_table_association" "private" {
count = length(var.availability_zones)
subnet_id = aws_subnet.private[count.index].id
route_table_id = aws_route_table.private[count.index].id
}
# Database Route Table (no internet access)
resource "aws_route_table" "database" {
vpc_id = aws_vpc.main.id
tags = {
Name = "${var.environment}-database-rt"
}
}
resource "aws_route_table_association" "database" {
count = length(var.availability_zones)
subnet_id = aws_subnet.database[count.index].id
route_table_id = aws_route_table.database.id
}
# Data source for AZs
data "aws_availability_zones" "available" {
state = "available"
}
Public and Private Subnet Design
Public Subnets
Public subnets are directly connected to the internet through an Internet Gateway. Resources in public subnets can initiate and receive traffic from the internet. Best practices for public subnets:
- Limited Use: Only place resources that need direct internet access
- No Sensitive Data: Never store secrets or sensitive databases here
- Use NAT for Outbound: Have private resources route through NAT, not directly to IGW
- Security Groups: Implement strict ingress rules on public resources
Private Subnets
Private subnets are isolated from direct internet access, providing a secure environment for application servers and databases. Resources in private subnets can initiate outbound connections through NAT Gateways but cannot receive unsolicited inbound traffic from the internet.
NAT Gateway Design and Management
Understanding NAT Gateways vs NAT Instances
NAT Gateways (Managed by AWS):
- High availability and scalability
- Automatic bandwidth scaling up to 45 Gbps
- Hourly charge + data processing charges
- Simple management
- Cannot be security group target
- Recommended for production
NAT Instances (Self-managed):
- Lower cost for light usage
- Full control and customization
- Manual high availability setup required
- Potential bottleneck under load
- Better for dev/test environments
CloudFormation: High Availability NAT Configuration
AWSTemplateFormatVersion: '2010-09-09'
Description: 'High Availability NAT Gateway Setup'
Parameters:
VpcId:
Type: AWS::EC2::VPC::Id
Description: VPC ID
PublicSubnet1:
Type: AWS::EC2::Subnet::Id
Description: Public subnet in AZ-1
PublicSubnet2:
Type: AWS::EC2::Subnet::Id
Description: Public subnet in AZ-2
Resources:
# Elastic IPs
NATGateway1EIP:
Type: AWS::EC2::EIP
DependsOn: AttachGateway
Properties:
Domain: vpc
Tags:
- Key: Name
Value: NAT-Gateway-1-EIP
NATGateway2EIP:
Type: AWS::EC2::EIP
DependsOn: AttachGateway
Properties:
Domain: vpc
Tags:
- Key: Name
Value: NAT-Gateway-2-EIP
# NAT Gateways
NATGateway1:
Type: AWS::EC2::NatGateway
Properties:
AllocationId: !GetAtt NATGateway1EIP.AllocationId
SubnetId: !Ref PublicSubnet1
Tags:
- Key: Name
Value: NAT-Gateway-1
NATGateway2:
Type: AWS::EC2::NatGateway
Properties:
AllocationId: !GetAtt NATGateway2EIP.AllocationId
SubnetId: !Ref PublicSubnet2
Tags:
- Key: Name
Value: NAT-Gateway-2
# CloudWatch Alarms for NAT Gateway Monitoring
NATGateway1ErrorPortAllocationAlarm:
Type: AWS::CloudWatch::Alarm
Properties:
AlarmName: NAT-Gateway-1-Error-Port-Allocation
AlarmDescription: Alert when NAT Gateway 1 runs out of ports
MetricName: ErrorPortAllocation
Namespace: AWS/NatGateway
Statistic: Sum
Period: 300
EvaluationPeriods: 1
Threshold: 1
ComparisonOperator: GreaterThanOrEqualToThreshold
Dimensions:
- Name: NatGatewayId
Value: !Ref NATGateway1
NATGateway1BytesOutAlarm:
Type: AWS::CloudWatch::Alarm
Properties:
AlarmName: NAT-Gateway-1-High-Data-Transfer
AlarmDescription: Alert when NAT Gateway 1 has high data transfer
MetricName: BytesOutToDestination
Namespace: AWS/NatGateway
Statistic: Sum
Period: 300
EvaluationPeriods: 2
Threshold: 10737418240 # 10 GB
ComparisonOperator: GreaterThanThreshold
Dimensions:
- Name: NatGatewayId
Value: !Ref NATGateway1
Outputs:
NATGateway1Id:
Description: ID of NAT Gateway 1
Value: !Ref NATGateway1
NATGateway2Id:
Description: ID of NAT Gateway 2
Value: !Ref NATGateway2
NATGateway1PublicIP:
Description: Elastic IP of NAT Gateway 1
Value: !Ref NATGateway1EIP
NATGateway2PublicIP:
Description: Elastic IP of NAT Gateway 2
Value: !Ref NATGateway2EIP
VPC Endpoints: Reducing Internet Traffic
VPC Endpoints allow you to connect privately to AWS services without routing traffic through the public internet. This improves security, reduces latency, and can lower data transfer costs.
Gateway Endpoints
Gateway endpoints are free and support only S3 and DynamoDB. They work by modifying your route tables to send traffic to these services through a direct connection.
Creating S3 Gateway Endpoint
AWSTemplateFormatVersion: '2010-09-09'
Description: 'VPC Gateway Endpoints for S3 and DynamoDB'
Parameters:
VpcId:
Type: AWS::EC2::VPC::Id
PrivateRouteTableId:
Type: String
Description: Route table ID for private subnets
Resources:
# S3 Gateway Endpoint
S3Endpoint:
Type: AWS::EC2::VPCEndpoint
Properties:
VpcId: !Ref VpcId
ServiceName: !Sub 'com.amazonaws.${AWS::Region}.s3'
VpcEndpointType: Gateway
RouteTableIds:
- !Ref PrivateRouteTableId
PolicyText:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal: '*'
Action:
- 's3:GetObject'
- 's3:PutObject'
- 's3:ListBucket'
Resource:
- 'arn:aws:s3:::my-app-bucket'
- 'arn:aws:s3:::my-app-bucket/*'
# DynamoDB Gateway Endpoint
DynamoDBEndpoint:
Type: AWS::EC2::VPCEndpoint
Properties:
VpcId: !Ref VpcId
ServiceName: !Sub 'com.amazonaws.${AWS::Region}.dynamodb'
VpcEndpointType: Gateway
RouteTableIds:
- !Ref PrivateRouteTableId
PolicyText:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal: '*'
Action:
- 'dynamodb:GetItem'
- 'dynamodb:PutItem'
- 'dynamodb:Query'
- 'dynamodb:Scan'
Resource: 'arn:aws:dynamodb:*:*:table/MyAppTable'
Outputs:
S3EndpointId:
Value: !Ref S3Endpoint
DynamoDBEndpointId:
Value: !Ref DynamoDBEndpoint
Interface Endpoints
Interface endpoints use AWS PrivateLink to provide private connectivity to AWS services. They work for most AWS services and are appropriate when you need more fine-grained access control.
Python Boto3: Creating Interface Endpoints Programmatically
import boto3
from botocore.exceptions import ClientError
def create_interface_endpoints(vpc_id, subnet_ids, security_group_ids):
"""
Create interface endpoints for common AWS services.
Args:
vpc_id: VPC ID where endpoints will be created
subnet_ids: List of subnet IDs (typically private subnets)
security_group_ids: List of security group IDs for endpoint access
"""
ec2_client = boto3.client('ec2')
# Services that commonly benefit from interface endpoints
services = [
'secretsmanager',
'ssm',
'ec2messages',
'ssmmessages',
'kms',
'logs',
'sns',
'sqs',
'sts',
]
endpoints = {}
for service in services:
try:
service_name = f'com.amazonaws.us-east-1.{service}'
response = ec2_client.create_vpc_endpoint(
VpcEndpointType='Interface',
ServiceName=service_name,
VpcId=vpc_id,
SubnetIds=subnet_ids,
SecurityGroupIds=security_group_ids,
PrivateDnsEnabled=True, # Use private DNS names
TagSpecifications=[
{
'ResourceType': 'vpc-endpoint',
'Tags': [
{'Key': 'Name', 'Value': f'{service}-endpoint'},
{'Key': 'Service', 'Value': service},
]
}
]
)
endpoint_id = response['VpcEndpoint']['VpcEndpointId']
endpoints[service] = endpoint_id
print(f'Created {service} endpoint: {endpoint_id}')
except ClientError as e:
print(f'Error creating {service} endpoint: {e}')
return endpoints
# Usage
vpc_id = 'vpc-12345678'
private_subnet_ids = ['subnet-11111111', 'subnet-22222222']
endpoint_sg_ids = ['sg-endpoint123']
endpoints = create_interface_endpoints(vpc_id, private_subnet_ids, endpoint_sg_ids)
print(f'\nCreated endpoints: {endpoints}')
Transit Gateway for Multi-VPC Connectivity
Transit Gateway simplifies connecting multiple VPCs, on-premises networks, and AWS Direct Connect. It acts as a central hub, eliminating the need for complex VPC peering meshes.
Terraform: Transit Gateway Hub-and-Spoke Architecture
# transit_gateway.tf
resource "aws_ec2_transit_gateway" "main" {
description = "Transit Gateway for multi-VPC connectivity"
default_route_table_association = "enable"
default_route_table_propagation = "enable"
dns_support = "enable"
vpn_ecn_support = "enable"
tags = {
Name = "production-tgw"
}
}
# Transit Gateway Route Table
resource "aws_ec2_transit_gateway_route_table" "main" {
transit_gateway_id = aws_ec2_transit_gateway.main.id
tags = {
Name = "production-tgw-rt"
}
}
# VPC Attachments
resource "aws_ec2_transit_gateway_vpc_attachment" "production_vpcs" {
count = length(var.production_vpc_ids)
transit_gateway_id = aws_ec2_transit_gateway.main.id
vpc_id = var.production_vpc_ids[count.index]
subnet_ids = var.production_subnet_ids[count.index]
transit_gateway_default_route_table_association = true
transit_gateway_default_route_table_propagation = true
tags = {
Name = "vpc-attachment-${count.index + 1}"
}
}
# CloudWatch Monitoring
resource "aws_cloudwatch_log_group" "tgw_flow_logs" {
name = "/aws/transit-gateway/flow-logs"
retention_in_days = 30
tags = {
Name = "tgw-flow-logs"
}
}
resource "aws_ec2_transit_gateway_route_table" "spoke_routes" {
for_each = toset(var.spoke_vpc_ids)
transit_gateway_id = aws_ec2_transit_gateway.main.id
tags = {
Name = "spoke-rt-${each.value}"
}
}
# Variables
variable "production_vpc_ids" {
type = list(string)
description = "VPC IDs for production VPCs"
}
variable "production_subnet_ids" {
type = list(list(string))
description = "Subnet IDs for VPC attachments"
}
variable "spoke_vpc_ids" {
type = list(string)
description = "VPC IDs for spoke VPCs"
}
VPC Peering for VPC-to-VPC Connectivity
VPC peering allows direct network connectivity between two VPCs without routing through the internet. However, with more than a few VPCs, Transit Gateway becomes more manageable.
CloudFormation: VPC Peering Setup
AWSTemplateFormatVersion: '2010-09-09'
Description: 'VPC Peering Connection'
Parameters:
SourceVpcId:
Type: AWS::EC2::VPC::Id
Description: Source VPC ID
DestinationVpcId:
Type: AWS::EC2::VPC::Id
Description: Destination VPC ID
SourceRouteTableId:
Type: String
Description: Route table in source VPC
DestinationRouteTableId:
Type: String
Description: Route table in destination VPC
Resources:
# VPC Peering Connection
PeeringConnection:
Type: AWS::EC2::VPCPeeringConnection
Properties:
VpcId: !Ref SourceVpcId
PeerVpcId: !Ref DestinationVpcId
Tags:
- Key: Name
Value: vpc-peer-connection
# Accept peering connection (if in same account)
PeeringAccept:
Type: AWS::EC2::VPCPeeringConnectionAccepter
Properties:
VpcPeeringConnectionId: !Ref PeeringConnection
# Routes in source VPC
SourceRoute:
Type: AWS::EC2::Route
DependsOn: PeeringAccept
Properties:
RouteTableId: !Ref SourceRouteTableId
DestinationCidrBlock: 10.1.0.0/16 # Destination VPC CIDR
VpcPeeringConnectionId: !Ref PeeringConnection
# Routes in destination VPC
DestinationRoute:
Type: AWS::EC2::Route
DependsOn: PeeringAccept
Properties:
RouteTableId: !Ref DestinationRouteTableId
DestinationCidrBlock: 10.0.0.0/16 # Source VPC CIDR
VpcPeeringConnectionId: !Ref PeeringConnection
Outputs:
PeeringConnectionId:
Description: VPC Peering Connection ID
Value: !Ref PeeringConnection
Security Groups and Network ACLs
Security Groups (Stateful Firewall)
Security groups act as stateful firewalls at the instance level. When you allow inbound traffic, response traffic is automatically allowed outbound.
Best Practices Security Group Design
AWSTemplateFormatVersion: '2010-09-09'
Resources:
# ALB Security Group
ALBSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: ALB Security Group
VpcId: !Ref VpcId
SecurityGroupIngress:
# HTTPS from anywhere
- IpProtocol: tcp
FromPort: 443
ToPort: 443
CidrIp: 0.0.0.0/0
Description: HTTPS from internet
# HTTP (for redirect to HTTPS)
- IpProtocol: tcp
FromPort: 80
ToPort: 80
CidrIp: 0.0.0.0/0
Description: HTTP from internet
SecurityGroupEgress:
- IpProtocol: -1
CidrIp: 0.0.0.0/0
Description: All outbound traffic
Tags:
- Key: Name
Value: alb-sg
# Application Server Security Group
AppSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Application Server Security Group
VpcId: !Ref VpcId
SecurityGroupIngress:
# From ALB only
- IpProtocol: tcp
FromPort: 8080
ToPort: 8080
SourceSecurityGroupId: !Ref ALBSecurityGroup
Description: From ALB
SecurityGroupEgress:
- IpProtocol: -1
CidrIp: 0.0.0.0/0
Description: All outbound
Tags:
- Key: Name
Value: app-sg
# Database Security Group
DBSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Database Security Group
VpcId: !Ref VpcId
SecurityGroupIngress:
# From app servers only
- IpProtocol: tcp
FromPort: 5432
ToPort: 5432
SourceSecurityGroupId: !Ref AppSecurityGroup
Description: PostgreSQL from app servers
Tags:
- Key: Name
Value: db-sg
Network ACLs (Stateless Firewall)
Network ACLs provide subnet-level stateless filtering. While more granular than security groups, they require careful planning of rule ordering and ephemeral port ranges.
VPC Flow Logs for Network Monitoring
VPC Flow Logs capture information about IP traffic to and from your VPC. They're essential for security analysis, troubleshooting connectivity issues, and compliance auditing.
Python: Setting Up VPC Flow Logs with Analysis
import boto3
import json
from datetime import datetime, timedelta
class VPCFlowLogManager:
def __init__(self):
self.ec2_client = boto3.client('ec2')
self.logs_client = boto3.client('logs')
self.athena_client = boto3.client('athena')
def enable_vpc_flow_logs(self, vpc_id, log_group_name, role_arn):
"""Enable VPC Flow Logs for comprehensive traffic monitoring."""
try:
response = self.ec2_client.create_flow_logs(
ResourceType='VPC',
ResourceIds=[vpc_id],
TrafficType='ALL',
LogDestinationType='cloud-watch-logs',
LogGroupName=log_group_name,
DeliverLogsPermissionIam=role_arn,
LogFormat='${version} ${account-id} ${interface-id} ${srcaddr} ${dstaddr} ${srcport} ${dstport} ${protocol} ${packets} ${bytes} ${windowstart} ${windowend} ${action} ${tcpflags} ${type} ${pkt-srcaddr} ${pkt-dstaddr}',
Tags={
'Name': f'{vpc_id}-flow-logs',
'Purpose': 'Network-Monitoring'
}
)
return response
except Exception as e:
print(f'Error enabling Flow Logs: {e}')
return None
def query_rejected_traffic(self, log_group_name, hours=1):
"""Query rejected traffic for security analysis."""
query = f'''
fields @timestamp, srcaddr, dstaddr, srcport, dstport, action
| filter action = "REJECT"
| stats count() as rejected_count by srcaddr, dstaddr
| sort rejected_count desc
'''
try:
response = self.logs_client.start_query(
logGroupName=log_group_name,
startTime=int((datetime.now() - timedelta(hours=hours)).timestamp()),
endTime=int(datetime.now().timestamp()),
queryString=query
)
query_id = response['queryId']
self._wait_for_query(query_id)
results = self.logs_client.get_query_results(queryId=query_id)
return self._parse_results(results['results'])
except Exception as e:
print(f'Error querying rejected traffic: {e}')
return None
def _wait_for_query(self, query_id):
"""Wait for CloudWatch Insights query to complete."""
import time
while True:
response = self.logs_client.get_query_status(queryId=query_id)
if response['status'] == 'Complete':
break
time.sleep(1)
def _parse_results(self, results):
"""Parse CloudWatch Insights query results."""
parsed = []
for record in results:
entry = {}
for field in record:
entry[field['field']] = field['value']
parsed.append(entry)
return parsed
# Usage
manager = VPCFlowLogManager()
# Enable Flow Logs
flow_log_response = manager.enable_vpc_flow_logs(
vpc_id='vpc-12345678',
log_group_name='/aws/vpc/flow-logs',
role_arn='arn:aws:iam::123456789012:role/flowlogsRole'
)
# Query rejected traffic
rejected = manager.query_rejected_traffic(
log_group_name='/aws/vpc/flow-logs',
hours=24
)
print('Rejected traffic sources:')
for entry in rejected:
print(f" {entry.get('srcaddr', 'N/A')} -> {entry.get('dstaddr', 'N/A')}: {entry.get('rejected_count', 'N/A')} rejects")
Hybrid Connectivity: Connecting to On-Premises Networks
Site-to-Site VPN
VPN provides encrypted connectivity to on-premises networks. It's suitable for moderate bandwidth requirements and is more cost-effective than Direct Connect for initial deployments.
AWS Direct Connect
Direct Connect provides dedicated network connections to AWS. It offers consistent network performance, lower latency, and higher bandwidth compared to internet-based VPNs.
Terraform: Site-to-Site VPN Configuration
resource "aws_vpn_gateway" "main" {
vpc_id = aws_vpc.main.id
amazon_side_asn = 64512
availability_zone = "us-east-1a"
tags = {
Name = "vpn-gateway"
}
}
resource "aws_customer_gateway" "on_premises" {
bgp_asn = 65000
public_ip = "203.0.113.12" # Your on-premises public IP
type = "ipsec.1"
tags = {
Name = "on-premises-gateway"
}
}
resource "aws_vpn_connection" "main" {
vpn_gateway_id = aws_vpn_gateway.main.id
customer_gateway_id = aws_customer_gateway.on_premises.id
type = "ipsec.1"
static_routes_only = false
tags = {
Name = "vpn-connection"
}
}
resource "aws_vpn_gateway_route_propagation" "main" {
vpn_gateway_id = aws_vpn_gateway.main.id
route_table_id = aws_route_table.private.id
}
# Enable VPN Gateway attachment
resource "aws_vpn_gateway_attachment" "main" {
vpc_id = aws_vpc.main.id
vpn_gateway_id = aws_vpn_gateway.main.id
}
Working with Warqline
We are a cloud engineering consultancy and an official AWS and Google Cloud partner. If you are running this in production and want a second pair of eyes, we scope work in a free 45-minute technical call: you describe what you are running and what worries you, and we tell you what we would look at first.
VPC Design Best Practices Summary
- Plan CIDR Blocks Carefully: Reserve sufficient address space for growth across all environments
- Implement Multi-AZ Architecture: Distribute resources across at least 2 AZs for high availability
- Use Three-Tier Subnets: Separate public, private, and database subnets for security
- Deploy Redundant NAT: One NAT Gateway per AZ for high availability
- Leverage VPC Endpoints: Reduce internet traffic and costs with gateway and interface endpoints
- Choose Connectivity Model: Use Transit Gateway for multiple VPCs, VPC peering for few connections
- Implement Proper Security: Use security groups at instance level, NACLs at subnet level for defense in depth
- Enable Flow Logs: Capture all network traffic for security analysis and troubleshooting
- Monitor and Alert: Use CloudWatch metrics and alarms for NAT Gateway, Transit Gateway, and VPN connections
- Plan for Hybrid: Design with future on-premises connectivity in mind
Conclusion: Building Scalable VPC Infrastructure
A well-designed VPC forms the backbone of your entire AWS infrastructure. By following these patterns and best practices—implementing multi-AZ architecture, proper network segmentation, secure connectivity, and comprehensive monitoring—you create a foundation that scales reliably and securely with your organization.
The key is to plan ahead while remaining flexible. Start with the three-tier architecture pattern, use Transit Gateway for multi-VPC connectivity, implement security groups thoughtfully, and monitor your network with Flow Logs. Regularly review and optimize your VPC configuration as your environment evolves and your requirements change.
Visit Warqline today to assess your VPC architecture and receive specific recommendations for improvement based on AWS Well-Architected Framework best practices.