AWS for DevOps — Subnets & Route Tables

3 minute read

4.2 — Subnets & Route Tables

Subnets divide your VPC into segments. Route tables control where network traffic is directed.

Subnets

A subnet lives in one Availability Zone and has its own CIDR range (a subset of the VPC CIDR):

  VPC: 10.0.0.0/16
  │
  ├── AZ: ap-south-1a
  │   ├── Public:  10.0.1.0/24   (251 usable IPs)
  │   ├── Private: 10.0.10.0/24  (251 usable IPs)
  │   └── Data:    10.0.20.0/24  (251 usable IPs)
  │
  ├── AZ: ap-south-1b
  │   ├── Public:  10.0.2.0/24
  │   ├── Private: 10.0.11.0/24
  │   └── Data:    10.0.21.0/24
  │
  └── AZ: ap-south-1c
      ├── Public:  10.0.3.0/24
      ├── Private: 10.0.12.0/24
      └── Data:    10.0.22.0/24

Creating Subnets

# Public subnet in AZ-a
$ aws ec2 create-subnet \
    --vpc-id vpc-0abc123 \
    --cidr-block 10.0.1.0/24 \
    --availability-zone ap-south-1a \
    --tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=public-1a}]'
{
    "Subnet": {
        "SubnetId": "subnet-pub1a",
        "CidrBlock": "10.0.1.0/24",
        "AvailabilityZone": "ap-south-1a"
    }
}

# Private subnet in AZ-a
$ aws ec2 create-subnet \
    --vpc-id vpc-0abc123 \
    --cidr-block 10.0.10.0/24 \
    --availability-zone ap-south-1a \
    --tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=private-1a}]'

# Enable auto-assign public IP for public subnets
$ aws ec2 modify-subnet-attribute \
    --subnet-id subnet-pub1a \
    --map-public-ip-on-launch

What Makes a Subnet Public or Private?

A subnet is public if its route table has a route to an Internet Gateway. A subnet is private if it does not.

Subnet Type Route to IGW Route to NAT Use Case
Public ✅ Yes Not needed ALB, bastion, NAT Gateway
Private (with NAT) ❌ No ✅ Yes App servers, ECS tasks
Private (isolated) ❌ No ❌ No Databases, internal services

Route Tables

A route table contains rules (routes) that determine where traffic goes:

  Public Route Table:
  ┌──────────────────┬─────────────────────┐
  │  Destination     │  Target              │
  ├──────────────────┼─────────────────────┤
  │  10.0.0.0/16     │  local               │
  │  0.0.0.0/0       │  igw-abc123          │
  └──────────────────┴─────────────────────┘

  Private Route Table:
  ┌──────────────────┬─────────────────────┐
  │  Destination     │  Target              │
  ├──────────────────┼─────────────────────┤
  │  10.0.0.0/16     │  local               │
  │  0.0.0.0/0       │  nat-def456          │
  └──────────────────┴─────────────────────┘
  • local route — traffic within the VPC stays within the VPC (always present, cannot be removed)
  • 0.0.0.0/0 — default route for all non-VPC traffic

Creating Route Tables

# Create a public route table
$ aws ec2 create-route-table \
    --vpc-id vpc-0abc123 \
    --tag-specifications 'ResourceType=route-table,Tags=[{Key=Name,Value=public-rt}]'
{
    "RouteTable": {
        "RouteTableId": "rtb-pub123"
    }
}

# Add a route to the Internet Gateway
$ aws ec2 create-route \
    --route-table-id rtb-pub123 \
    --destination-cidr-block 0.0.0.0/0 \
    --gateway-id igw-abc123

# Associate the route table with the public subnet
$ aws ec2 associate-route-table \
    --route-table-id rtb-pub123 \
    --subnet-id subnet-pub1a

Production Subnet Layout

For high availability, repeat your subnet pattern across at least two (ideally three) AZs:

# Create all subnets in a loop (conceptual — run each individually)
# Public subnets
$ for az_suffix in a b c; do
    aws ec2 create-subnet \
        --vpc-id vpc-0abc123 \
        --cidr-block "10.0.${az_suffix_num}.0/24" \
        --availability-zone "ap-south-1${az_suffix}" \
        --tag-specifications "ResourceType=subnet,Tags=[{Key=Name,Value=public-1${az_suffix}}]"
done

Listing Subnets

$ aws ec2 describe-subnets \
    --filters "Name=vpc-id,Values=vpc-0abc123" \
    --query 'Subnets[*].[SubnetId,CidrBlock,AvailabilityZone,Tags[?Key==`Name`].Value|[0]]' \
    --output table
-----------------------------------------------------------
|  subnet-pub1a  | 10.0.1.0/24  | ap-south-1a | public-1a  |
|  subnet-prv1a  | 10.0.10.0/24 | ap-south-1a | private-1a |
|  subnet-pub1b  | 10.0.2.0/24  | ap-south-1b | public-1b  |
|  subnet-prv1b  | 10.0.11.0/24 | ap-south-1b | private-1b |
-----------------------------------------------------------

Common Mistakes

Mistake Why It Hurts Fix
All subnets in one AZ Single AZ failure takes everything down Spread across 2-3 AZs
Overlapping CIDR blocks Cannot peer VPCs with overlapping ranges Plan CIDR allocation upfront
Not tagging subnets Can’t tell public from private Tag: Name, Type=public/private
Using the main route table for public routing New subnets auto-associate and become public Create explicit route tables

Quick Reference

Task Command
Create subnet aws ec2 create-subnet --vpc-id VPC --cidr-block CIDR --availability-zone AZ
Auto-assign public IP aws ec2 modify-subnet-attribute --subnet-id SUBNET --map-public-ip-on-launch
Create route table aws ec2 create-route-table --vpc-id VPC
Add route aws ec2 create-route --route-table-id RT --destination-cidr-block CIDR --gateway-id GW
Associate RT to subnet aws ec2 associate-route-table --route-table-id RT --subnet-id SUBNET
List subnets aws ec2 describe-subnets --filters Name=vpc-id,Values=VPC

Next → Internet & NAT Gateways

Updated: