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:

  1. Current Requirements: How many resources do you need today?
  2. Future Growth: Plan for 2-3 years of expansion
  3. VPC Peering and Hybrid Connectivity: Ensure no overlaps with on-premises networks or other VPCs
  4. 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:

  1. 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
  2. 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
  3. 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:

  1. Limited Use: Only place resources that need direct internet access
  2. No Sensitive Data: Never store secrets or sensitive databases here
  3. Use NAT for Outbound: Have private resources route through NAT, not directly to IGW
  4. 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.

Talk to an engineer

VPC Design Best Practices Summary

  1. Plan CIDR Blocks Carefully: Reserve sufficient address space for growth across all environments
  2. Implement Multi-AZ Architecture: Distribute resources across at least 2 AZs for high availability
  3. Use Three-Tier Subnets: Separate public, private, and database subnets for security
  4. Deploy Redundant NAT: One NAT Gateway per AZ for high availability
  5. Leverage VPC Endpoints: Reduce internet traffic and costs with gateway and interface endpoints
  6. Choose Connectivity Model: Use Transit Gateway for multiple VPCs, VPC peering for few connections
  7. Implement Proper Security: Use security groups at instance level, NACLs at subnet level for defense in depth
  8. Enable Flow Logs: Capture all network traffic for security analysis and troubleshooting
  9. Monitor and Alert: Use CloudWatch metrics and alarms for NAT Gateway, Transit Gateway, and VPN connections
  10. 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.