A production-ready AWS VPC isn’t just a collection of subnets; it’s a carefully architected network fabric that dictates how your applications communicate, scale, and remain secure.

Let’s see this in action. Imagine we’re setting up a basic multi-tier web application: a public-facing load balancer, stateless web servers, and a stateful database.

Here’s a snippet of what the core VPC and subnet structure might look like using Terraform:

resource "aws_vpc" "main" {
  cidr_block = "10.0.0.0/16"
  enable_dns_support = true
  enable_dns_hostnames = true

  tags = {
    Name = "my-prod-vpc"
  }
}

# Public Subnets (for Load Balancers, NAT Gateways)
resource "aws_subnet" "public_a" {
  vpc_id            = aws_vpc.main.id
  cidr_block        = "10.0.1.0/24"
  availability_zone = "us-east-1a"
  map_public_ip_on_launch = true # For NAT Gateway, not instances

  tags = {
    Name = "my-prod-vpc-public-a"
  }
}

resource "aws_subnet" "public_b" {
  vpc_id            = aws_vpc.main.id
  cidr_block        = "10.0.2.0/24"
  availability_zone = "us-east-1b"
  map_public_ip_on_launch = true

  tags = {
    Name = "my-prod-vpc-public-b"
  }
}

# Private Subnets - App Tier (for Web Servers)
resource "aws_subnet" "private_app_a" {
  vpc_id            = aws_vpc.main.id
  cidr_block        = "10.0.10.0/24"
  availability_zone = "us-east-1a"

  tags = {
    Name = "my-prod-vpc-private-app-a"
  }
}

resource "aws_subnet" "private_app_b" {
  vpc_id            = aws_vpc.main.id
  cidr_block        = "10.0.11.0/24"
  availability_zone = "us-east-1b"

  tags = {
    Name = "my-prod-vpc-private-app-b"
  }
}

# Private Subnets - DB Tier (for Databases)
resource "aws_subnet" "private_db_a" {
  vpc_id            = aws_vpc.main.id
  cidr_block        = "10.0.20.0/24"
  availability_zone = "us-east-1a"

  tags = {
    Name = "my-prod-vpc-private-db-a"
  }
}

resource "aws_subnet" "private_db_b" {
  vpc_id            = aws_vpc.main.id
  cidr_block        = "10.0.21.0/24"
  availability_zone = "us-east-1b"

  tags = {
    Name = "my-prod-vpc-private-db-b"
  }
}

This setup addresses the fundamental problem of isolating resources and controlling traffic flow. The core idea is to segment your network based on trust levels and function.

  • Public Subnets: These are your gateways to the internet. Resources here, like Elastic Load Balancers (ELBs) or NAT Gateways, can have public IP addresses and are directly reachable from the outside world (with proper security group and NACL configurations, of course). We’ll typically place ELBs here to receive incoming traffic and NAT Gateways to allow instances in private subnets to initiate outbound connections.
  • Private Subnets (App Tier): These subnets host your application servers (e.g., EC2 instances running web applications). They are not directly reachable from the internet. All inbound traffic is proxied through the ELB in the public subnet. Outbound internet access for tasks like software updates or API calls is routed via a NAT Gateway in the public subnet.
  • Private Subnets (DB Tier): This is where your databases reside. These subnets are even more restricted. They are not reachable from the internet, nor are they typically directly reachable from the app tier subnets unless absolutely necessary. Communication should be explicitly allowed via security groups. This provides the strongest isolation for your sensitive data.

The key levers you control are:

  1. CIDR Blocks: Carefully choose your VPC and subnet CIDR ranges. Overlap with other networks (on-prem, other VPCs) is a common pitfall. A /16 for the VPC is standard, providing ample room, and /24 for subnets gives you 251 usable IPs per subnet, which is usually sufficient.
  2. Availability Zones (AZs): Distribute your subnets across multiple AZs within a region for high availability. Deploying resources across AZs ensures that if one data center fails, your application remains operational.
  3. Route Tables: This is where the magic happens. You define how traffic flows.
    • Public subnets’ route tables point 0.0.0.0/0 (all internet traffic) to an Internet Gateway (IGW).
    • Private subnets’ route tables point 0.0.0.0/0 to a NAT Gateway located in a public subnet.
    • Specific routes might be needed for VPC peering or VPN connections.
  4. Network Access Control Lists (NACLs): These are stateless firewalls at the subnet level. They are useful for broad, high-level blocking or allowing of traffic based on IP address and port. They operate independently of security groups.
  5. Security Groups: These are stateful firewalls attached to individual instances or resources. They control traffic at the instance level and are crucial for fine-grained access control between tiers (e.g., allowing the app tier to talk to the DB tier on port 3306).

The most surprising thing about designing these networks is how often the most critical security controls are overlooked. While security groups are essential for instance-level control, it’s the combination of route tables, NACLs, and the careful placement of resources across public and private subnets that truly enforces your security posture. For instance, by ensuring your database instances are only in private subnets and their security groups only allow inbound traffic from your application servers on the database port, you create a robust defense-in-depth.

The next step after building this VPC foundation is to implement robust DNS resolution and potentially integrate it with AWS PrivateLink for secure service access.

Want structured learning?

Take the full Aws course →