Skip to content
AWS aws networking 5 min read

When to Use Public vs Private Subnets

One of the first design decisions you make inside a Virtual Private Cloud (VPC — your own private network inside AWS) is where each resource lives: a public subnet or a private subnet. Getting this right is the single biggest lever you have for keeping your infrastructure secure. The rule is simple once you understand it: only the things that must talk to the internet directly go in public subnets, and everything else stays private. This page explains exactly what belongs where and why.

What makes a subnet “public” or “private”

A subnet (a slice of your VPC’s IP address range) is not labelled “public” or “private” by AWS. The label comes entirely from how its route table is configured. A route table is a set of rules that decides where network traffic goes.

  • A public subnet has a route that sends internet-bound traffic (0.0.0.0/0, meaning “any address anywhere”) to an Internet Gateway (IGW — the component that connects your VPC to the public internet).
  • A private subnet has no route to an Internet Gateway. If it needs outbound internet access, it routes through a NAT Gateway (Network Address Translation — lets private resources reach out without being reachable from outside) instead.

So the difference is one route table entry. A resource in a public subnet can be reached from the internet (if it has a public IP and the firewall allows it); a resource in a private subnet cannot be reached directly from the internet at all.

The rule: what goes where

The architectural rule that experienced teams follow:

ResourceSubnetWhy
Internet-facing load balancer (ALB/NLB)PublicMust accept inbound traffic from users on the internet
Bastion host / jump boxPublicThe single controlled entry point for SSH/RDP admin access
NAT GatewayPublicNeeds an Internet Gateway route to forward private subnet traffic outward
Application servers (EC2, ECS, EKS nodes)PrivateShould never be reached directly from the internet
Databases (RDS, Aurora, ElastiCache)PrivateHighest-value target; must be unreachable from outside
Internal-only load balancersPrivateOnly serve traffic from inside the VPC

The short version: only internet-facing entry points belong in public subnets. App servers and databases always go private.

Why this matters: the security rationale

Every resource with a public IP address in a public subnet is part of your attack surface — the set of things an attacker on the internet can try to reach. A database sitting in a public subnet with a public IP is directly exposed to the entire internet. Even with a tight firewall, you are one misconfigured rule away from a breach, and automated bots scan every public IP constantly.

By keeping app servers and databases private, you give them no public IP and no route to the Internet Gateway. There is simply no network path from the internet to them. An attacker would first have to compromise something in a public subnet (like a bastion or load balancer) and pivot inward. This is “defense in depth” — multiple layers, not one.

Gotcha: A very common beginner mistake is launching an EC2 app server or an RDS database into a public subnet with a public IP “so it’s easy to connect to.” This exposes your most valuable assets to the open internet. Keep them private. Reach the internet out via a NAT Gateway, and let users reach your app in through a load balancer.

How traffic actually flows in a good design

  • Inbound user traffic: Internet → Internet Gateway → public subnet → internet-facing load balancer → private subnet → app servers. Users never touch your servers directly.
  • Outbound from private resources (e.g. an app server downloading OS patches): private subnet → NAT Gateway (in a public subnet) → Internet Gateway → internet. The connection can only be initiated from inside; the internet cannot start a connection back.
  • Admin access: Your laptop → bastion host (public subnet) → app servers (private subnet) over SSH.

How to make a subnet public (console + CLI)

A subnet becomes public when its route table points 0.0.0.0/0 at an Internet Gateway. Assume you already have a VPC (vpc-0a1b2c3d), a subnet (subnet-0a1b2c3d), and an Internet Gateway (igw-0a1b2c3d) attached to the VPC.

When to use this

Do this only for the subnet that will hold load balancers, bastions, and NAT Gateways. Do not do it for subnets holding app servers or databases.

Console steps

  1. Open the VPC console and choose Route Tables in the left menu.
  2. Click Create route table, name it rtb-public, select your VPC, and create it.
  3. Select the new route table, open the Routes tab, and choose Edit routes.
  4. Add a route: Destination 0.0.0.0/0, Target = your Internet Gateway (igw-0a1b2c3d). Save.
  5. Open the Subnet associations tab, choose Edit subnet associations, tick your public subnet, and save.

AWS CLI (v2) equivalent

# Create the route table
aws ec2 create-route-table --vpc-id vpc-0a1b2c3d \
  --tag-specifications 'ResourceType=route-table,Tags=[{Key=Name,Value=rtb-public}]'

# Add the internet route (use the route-table-id returned above)
aws ec2 create-route --route-table-id rtb-0a1b2c3d \
  --destination-cidr-block 0.0.0.0/0 \
  --gateway-id igw-0a1b2c3d

# Associate the public subnet
aws ec2 associate-route-table --route-table-id rtb-0a1b2c3d \
  --subnet-id subnet-0a1b2c3d

Output:

{
    "AssociationId": "rtbassoc-0a1b2c3d4e5f67890",
    "AssociationState": {
        "State": "associated"
    }
}

A private subnet is simply a subnet associated with a route table that has no 0.0.0.0/0 → igw route (its default outbound route, if any, points at a NAT Gateway instead).

Verifying nothing is accidentally public

Auto-assigning public IPs is a frequent source of accidental exposure. Check and disable it on private subnets:

aws ec2 modify-subnet-attribute \
  --subnet-id subnet-0a1b2c3d \
  --no-map-public-ip-on-launch

Cost note: A NAT Gateway costs roughly $0.045 per hour (about $32/month) plus ~$0.045 per GB processed in most regions. For private subnets that only need a few outbound calls (e.g. reaching S3 or DynamoDB), a VPC endpoint is often cheaper and keeps traffic off the public internet entirely.

Best Practices

  • Put only load balancers, bastions, and NAT Gateways in public subnets; everything else goes private.
  • Never assign a public IP to an app server or database — disable auto-assign on private subnets.
  • Give private resources outbound internet access through a NAT Gateway, not an Internet Gateway.
  • Front all public inbound traffic with a load balancer; do not expose servers directly.
  • Spread public and private subnets across at least two Availability Zones for high availability.
  • Use security groups to allow only the exact ports and sources each tier needs (e.g. the database accepts traffic only from the app server’s security group).
  • Prefer VPC endpoints over NAT for AWS-internal traffic (S3, DynamoDB) to cut cost and exposure.
Last updated June 15, 2026
Was this helpful?