Skip to main content
Cloud & AWS·8 min read

VPC Network Segmentation on AWS — Public, Private, and Isolated Subnets

A practical guide to segmenting an AWS VPC into public, private, and isolated subnets across availability zones: route tables, NAT gateways, security groups vs NACLs, and a tiered web/app/data architecture.

DT

DevOps Engineer & Technical Writer

The Problem

A flat VPC — one big subnet with everything in it — works right up until it doesn't. The database is reachable from the internet, every instance can talk to every other instance, and a single compromised web server has a clear path to your data. Network segmentation fixes this by splitting the VPC into tiers with controlled routes between them, so that reachability is a deliberate decision rather than an accident of the default setup.

VPC SEGMENTATION — TIERED SUBNETS ACROSS TWO AZs Internet Gateway VPC 10.0.0.0/16 AZ-a AZ-b PUBLIC SUBNET ALB NAT GW PUBLIC SUBNET ALB PRIVATE APP SUBNET app servers (egress via NAT) PRIVATE APP SUBNET app servers ISOLATED DATA SUBNET RDS primary (no internet route) ISOLATED DATA SUBNET RDS standby traffic flows down the tiers; the data tier has no path to the internet

The idea is to layer the VPC so that each tier only has the network reach it genuinely needs. Public can reach the internet; private can reach out but not be reached from the internet; isolated can't touch the internet at all.

The Three Subnet Tiers

TierInternet inboundInternet outboundTypical contents
<strong>Public</strong>Yes (via IGW)Yes (via IGW)Load balancers, NAT gateways, bastion hosts
<strong>Private</strong>NoYes (via NAT)App servers, internal services, containers
<strong>Isolated</strong>NoNoDatabases, caches, anything that should never egress

What makes a subnet "public" or "private" is not a setting on the subnet — it's the route table attached to it.

Route Tables Define Everything

A subnet is public because its route table sends 0.0.0.0/0 to the Internet Gateway (IGW). A subnet is private because its default route points at a NAT Gateway. An isolated subnet has no 0.0.0.0/0 route at all — only the implicit local route for in-VPC traffic.

# Public route table: internet via the IGW

aws ec2 create-route \

--route-table-id rtb-public \

--destination-cidr-block 0.0.0.0/0 \

--gateway-id igw-0abc123

# Private route table: internet via the NAT gateway

aws ec2 create-route \

--route-table-id rtb-private \

--destination-cidr-block 0.0.0.0/0 \

--nat-gateway-id nat-0def456

# Isolated route table: no default route is added at all

NAT Gateway — Egress Without Exposure

Private instances still need outbound internet access to pull packages, call external APIs, or fetch updates. A NAT gateway lives in a public subnet and gives private instances outbound-only access: they can initiate connections out, but nothing on the internet can initiate a connection in.

Two practical notes:

  • NAT gateways are per-AZ. For high availability, run one NAT gateway in each AZ and point each AZ's private route table at its local NAT gateway. One NAT shared across AZs is a cross-AZ failure and cost risk.
  • NAT gateways cost money per hour and per GB. For purely isolated workloads, skip NAT entirely and use VPC endpoints to reach AWS services privately.

Spread Across Availability Zones

Each tier should exist in at least two AZs. If all your private app subnets live in one AZ and that AZ has an outage, your whole app goes down. A standard layout for a 10.0.0.0/16 VPC:

# AZ-a                        # AZ-b

10.0.0.0/24 public 10.0.1.0/24 public

10.0.10.0/24 private app 10.0.11.0/24 private app

10.0.20.0/24 isolated data 10.0.21.0/24 isolated data

RDS and other data services use the multi-AZ layout directly: the primary lives in one isolated subnet and the standby in another.

Security Groups vs NACLs

AWS gives you two network controls, and they operate at different levels:

Security GroupNetwork ACL
Attached toInstances / ENIsSubnets
State<strong>Stateful</strong> (return traffic auto-allowed)<strong>Stateless</strong> (must allow both directions)
RulesAllow onlyAllow and deny
EvaluationAll rulesIn rule-number order, first match wins

In practice, security groups do the real work and are where you enforce least privilege. NACLs are a coarse, subnet-wide backstop. A clean tiered design controls flow with security groups that reference each other:

{

"Comment": "App tier security group — only accept traffic from the ALB and only reach the DB",

"Ingress": [

{ "Protocol": "tcp", "Port": 8080, "Source": "sg-alb" }

],

"Egress": [

{ "Protocol": "tcp", "Port": 5432, "Destination": "sg-database" }

]

}

Referencing security groups by ID (sg-alb, sg-database) instead of CIDR ranges is the key trick: the rule stays correct even as instances come and go, and it encodes the tier relationships directly.

Least-Privilege Flow

Wire the tiers so each one only talks to its neighbor:

  • ALB security group — allows 443 from the internet.
  • App security group — allows 8080 only from the ALB's security group.
  • Database security group — allows 5432 only from the app's security group.

The result: the internet can reach only the ALB, the ALB can reach only the app, and the app can reach only the database. There is no direct path from the internet to your data.

Common Mistakes to Avoid

  • A single AZ. No redundancy. Spread every tier across at least two AZs.
  • Databases in public subnets. The classic breach vector. Data belongs in isolated subnets with no internet route.
  • Opening security groups to 0.0.0.0/0. Only the public-facing load balancer should accept traffic from everywhere. Everything behind it should reference the tier in front of it.
  • One NAT gateway for all AZs. Creates cross-AZ dependency and data-transfer cost, and the whole private tier loses egress if that AZ fails.
  • Relying on NACLs for fine-grained rules. NACLs are stateless and order-sensitive — easy to misconfigure. Enforce least privilege with security groups and keep NACLs simple.

FAQ

What makes a subnet public or private in AWS?

Nothing on the subnet itself — it's the route table. A subnet is public when its route table sends 0.0.0.0/0 to an Internet Gateway, and private when that default route points to a NAT gateway instead. An isolated subnet has no 0.0.0.0/0 route at all.

What is the difference between a security group and a NACL?

A security group is stateful and attaches to instances, automatically allowing return traffic and supporting allow rules only. A NACL is stateless and attaches to a subnet, evaluating numbered allow and deny rules in order and requiring you to permit both directions of traffic. Use security groups for most access control and NACLs as a coarse subnet-level backstop.

Why do I need a NAT gateway?

Instances in private subnets have no route to the internet, but they often need outbound access to download updates or call external APIs. A NAT gateway, placed in a public subnet, lets them initiate outbound connections while blocking any inbound connections from the internet. For reaching AWS services specifically, VPC endpoints can replace NAT and avoid its data-transfer cost.

How many availability zones should my VPC span?

At least two, with every tier replicated across them. Spanning multiple AZs means an outage in one zone doesn't take down your whole application, and it's required for high-availability features like multi-AZ RDS and cross-zone load balancing. Three AZs is common for production workloads that need extra resilience.