← Computer Networks
Distributed & Cloud Networking

VPC & Subnets — Public/Private, Security Groups

Watch: "AWS VPC Beginner to Pro — Virtual Private Cloud Tutorial" — freeCodeCamp.org

A Virtual Private Cloud is your own logically isolated slice of a cloud provider's network — the foundation every other AWS/GCP/Azure networking concept sits on top of. Interviewers probe VPC design to check that you can reason about network isolation and blast radius, not just spin up EC2 instances.

What Is a VPC?

A VPC (Virtual Private Cloud) is your own private, walled-off section of a cloud provider's network — like renting a whole floor of a building instead of sharing open space with every other tenant. Nothing outside your VPC can see or reach what's inside it unless you explicitly allow it, even though it's all physically running on the same shared cloud infrastructure as everyone else's.

What Is a Subnet?

A subnet is a smaller section carved out of that VPC, used to group things that should be treated the same way — for example, a "public" subnet for anything that needs to be reachable from the internet, and a "private" subnet for things like databases that never should be. Each subnet gets its own rules about what can come in and out, so a mistake in one part of the network doesn't automatically expose everything else.

Where This Is Used

A Day-to-Day Example

Think of an office building. The VPC is the building itself — its own address, its own front door, separate from every other building on the street. Subnets are the individual floors inside it: the lobby (public subnet) that any visitor can walk into, and the records room upstairs (private subnet) that only badge-holders can reach, with a security guard (security group) checking exactly who's allowed through each specific door.

Deep Dive: Anatomy of a Production VPC

Almost every real backend follows the same three-tier shape: a public-facing edge, a private application tier, and a private data tier — each one a subnet, each one with a different route table controlling what it can reach.

Three-tier VPC: public, app and data subnetspublic subnet 10.0.1.0/24 · route 0.0.0.0/0 → IGWprivate app subnet 10.0.2.0/24 · outbound only via NATprivate data subnet 10.0.3.0/24 · no internet route at allinternetinternet gatewayload balancerNAT gatewaybackend / EKS nodesRDS PostgreSQLElastiCacheSG: from ALB onlyoutboundSG: from app tier
Only the load balancer and NAT gateway live in the public subnet. App servers are private and can reach out only through NAT; databases have no internet route in either direction. Security groups chain the tiers: the app tier only accepts the load balancer, the data tier only accepts the app tier.
publicapp tierdata tier
Show text version
VPC: 10.0.0.0/16

  Internet
     │
     ▼
  Internet Gateway (IGW)
     │
     ▼
  Public Subnet   10.0.1.0/24   (AZ-a)
     - Application Load Balancer
     - NAT Gateway
     - route table: 0.0.0.0/0 → IGW
     │
     ▼  (only via the ALB's target group; nothing else in here is
         directly reachable from the internet)
  Private Subnet  10.0.2.0/24   (AZ-a)      — "app tier"
     - Backend service instances / EKS nodes
     - route table: 0.0.0.0/0 → NAT Gateway (outbound only)
     - Security Group: inbound only from ALB's SG, on the app port
     │
     ▼
  Private Subnet  10.0.3.0/24   (AZ-a)      — "data tier"
     - RDS/PostgreSQL, ElastiCache/Redis
     - route table: no 0.0.0.0/0 route at all — no internet access,
       inbound or outbound
     - Security Group: inbound only from app tier's SG, on the DB port

Notice what makes each subnet public or private isn't a checkbox — it's purely its route table. The public subnet's route table sends 0.0.0.0/0 traffic to the Internet Gateway. The app-tier private subnet's route table sends 0.0.0.0/0 to a NAT Gateway instead (outbound internet access for pulling packages/updates, but nothing can initiate a connection in from outside). The data-tier subnet often has no 0.0.0.0/0 route at all, because a database has no legitimate reason to talk to the public internet in either direction — that's the strongest isolation you can give it short of a fully air-gapped network.

Deep Dive: Security Groups vs Network ACLs — Stateful vs Stateless

Both are firewalls, but they operate at different layers with a fundamentally different execution model, and the distinction is one of the most common interview trip-ups.

Stateful security group versus stateless network ACLsecurity group: remembers the connectionALBsecurity groupstatefulbackend :8080in :8080 ✓reply allowed automaticallynetwork ACL: judges each packet on its ownALBnetwork ACLstatelessbackend :8080in :8080 ✓reply to port 51342 needs its own outbound rule
The response to an allowed request goes back to the client's random ephemeral port. A security group tracks the connection and lets it out automatically; a network ACL doesn't, so without an outbound rule for ports 1024–65535 the reply is silently dropped and the connection hangs.
statefulstatelessdropped unless allowed
Show text version
Security Group (attached to the backend instance's ENI)
  Inbound rules:
    ALLOW  tcp/8080  from sg-alb (the load balancer's security group)
  Outbound rules:
    ALLOW  all traffic  to 0.0.0.0/0        (default)

  A request comes IN on 8080 from the ALB → allowed by the inbound rule.
  The RESPONSE going back out is automatically allowed too — the SG is
  stateful, it remembers the connection and doesn't need a matching
  outbound rule for return traffic.

Network ACL (attached to the private subnet itself)
  Inbound rules:                         Outbound rules:
    100  ALLOW  tcp/8080  from 10.0.1.0/24   100  ALLOW  tcp/1024-65535 to 10.0.1.0/24
    *    DENY   all                          *    DENY   all

  Same request comes IN on 8080 → allowed by inbound rule 100.
  The RESPONSE going back out uses an EPHEMERAL port (e.g. 51342), not
  8080 — because the NACL is stateless, it does NOT remember the
  inbound connection, so you must have a separate outbound rule
  explicitly allowing the ephemeral port range, or return traffic is
  silently dropped and the connection just hangs.

That ephemeral-port detail is the classic NACL debugging trap in practice: a NACL that only allows inbound on the app port, with no matching outbound rule for the high ephemeral port range, breaks connectivity in a way that's confusing to diagnose — the SYN gets in, the app processes it, but the response can't get back out.

Key Pointers

How It Works

  1. Create a VPC with a CIDR block, then carve out subnets — at least one public and one private per Availability Zone for high availability.
  2. Attach an Internet Gateway to the VPC and add a default-route entry to it in the public subnet's route table.
  3. Launch a NAT Gateway inside a public subnet; add a default route to it from each private subnet's route table so private instances can reach the internet (e.g. to pull packages) without being reachable from it.
  4. Attach security groups to individual instances/resources to control who can talk to them at L3/L4 (source IP/SG, protocol, port).
  5. Optionally layer NACLs on the subnets themselves for a coarser, defense-in-depth boundary — e.g. explicitly blocking a known-bad CIDR range at the subnet level regardless of any instance's security group.

Try It Yourself

With AWS CLI access, you can read a real VPC's structure directly — this is also exactly what you'd do to debug a "my private instance can't reach the internet" ticket:

# List subnets in a VPC, and see which are public vs private at a glance
aws ec2 describe-subnets --filters "Name=vpc-id,Values=vpc-0abc123" \
  --query 'Subnets[].{ID:SubnetId,CIDR:CidrBlock,AZ:AvailabilityZone,MapPublicIp:MapPublicIpOnLaunch}'

# Read the route table for a subnet — the real source of truth for
# public vs private (look for a 0.0.0.0/0 route and where it points)
aws ec2 describe-route-tables \
  --filters "Name=association.subnet-id,Values=subnet-0def456"

# Inspect a security group's inbound/outbound rules
aws ec2 describe-security-groups --group-ids sg-0789abc \
  --query 'SecurityGroups[].IpPermissions'

# Inspect a NACL — note it shows numbered rules evaluated in order,
# unlike a security group's unordered allow-list
aws ec2 describe-network-acls \
  --filters "Name=association.subnet-id,Values=subnet-0def456"

Interview Q&A

Q: A backend instance in a private subnet can't reach the internet to pull an OS update. What do you check?

A: In order: does the private subnet's route table have a 0.0.0.0/0 route to a NAT Gateway; is that NAT Gateway actually running and in a public subnet with its own route to an Internet Gateway; does the instance's security group allow the outbound traffic (usually yes by default, but worth confirming); and does the subnet's NACL allow both the outbound request and, since NACLs are stateless, the inbound ephemeral-port response. Any one of those four missing breaks it, and they fail silently in different ways so you check them in that order.

Q: What's the actual difference between a security group and a NACL — not just "one's stateful"?

A: Security groups attach to individual resources (an ENI) and only ever allow — there's no explicit deny, so anything not allowed is implicitly denied. NACLs attach to a subnet (affecting every resource in it) and evaluate numbered rules in order, allowing explicit allow AND explicit deny. The stateful/stateless distinction is the practical trap: a security group automatically permits return traffic for a connection it allowed in; a NACL does not, so you need a matching rule for the response's ephemeral port range or the connection hangs even though the initial packet got through.

Q: Does putting a resource in a public subnet make it internet-accessible?

A: Not by itself. "Public subnet" only means the subnet's route table has a path to an Internet Gateway. The resource inside it still needs a public IP or Elastic IP address, and its security group still needs to allow the inbound traffic. A resource can sit in a public subnet with no public IP and be just as unreachable from the internet as one in a private subnet.

Q: Why put the database in a private subnet instead of relying on a security group alone?

A: Defense in depth — a security group is one control that could be misconfigured (e.g. someone accidentally opens 0.0.0.0/0). Putting the database in a subnet with no route to an Internet Gateway at all means even a misconfigured security group can't make it internet-reachable, because there's no network path for the traffic to take in the first place. It's a structural guarantee, not a policy one.

System Design Tie-in

Any "design a scalable web application" prompt eventually needs a concrete network layout, and the three-tier VPC shape above is the expected answer: a public subnet for the load balancer and NAT gateway (the only things with any internet-facing surface), a private subnet for stateless backend/application instances that scale horizontally behind the load balancer, and a further-isolated private subnet for the database and cache with no internet route at all. Spreading each tier across multiple Availability Zones (and therefore multiple subnets, one pair per AZ) is what actually gets you high availability — a single-AZ VPC design is a common gap interviewers probe for by asking "what happens if that AZ goes down."

Trade-offs

Security groups are the workhorse for day-to-day access control — stateful rules are easier to reason about and manage per-service. NACLs are coarser and stateless, so they're used sparingly, mainly as a subnet-wide backstop (e.g. explicit deny of a specific IP range) rather than primary access control — get the inbound/outbound pairing wrong on a NACL and you silently drop return traffic, a classic debugging trap. The other frequent interview gotcha: "public subnet" doesn't mean instances there automatically have public IPs, and a route to an IGW doesn't make a private subnet's resources internet-reachable — the resource still needs a public IP/Elastic IP, and a NAT Gateway provides outbound-only access, never inbound.