A packet's walk through a VPC, in AWS

cloudnetworkaws

Open inbound port 80 on a security group. Leave outbound 80 closed. Contradiction?

It sounds like one. It isn't.

That server answers web clients all day. It just can't open port 80 to call out on its own. One word hides the trick: stateful. To see it, follow one packet from the internet to an EC2 instance, and watch which walls it hits.

The five things a packet meets

A VPC has a small cast, and each member does exactly one job.

  • Internet Gateway (IGW) attaches to the VPC itself, one per VPC. It is the one door to the internet.
  • Route Table (RT) decides where traffic goes next.
  • Subnet is a box for resources. It carries one RT and one NACL.
  • Network Access Control List (NACL) filters at the subnet edge.
  • Security Group (SG) filters at the instance.

The "VPC router" you see in architecture diagrams is not a real box. It is a picture of the RTs.

What actually makes a subnet "public"

Not a checkbox. A route.

A subnet is public when its RT holds an entry that sends traffic to the IGW. That entry, and nothing else, is the definition:

0.0.0.0/0     igw-xxxxx    # everything else: out to the internet
10.0.0.0/24   local        # traffic inside the VPC (default, unchangeable)

Those /24 and /16 blocks are just numbers, not places. The same block can live in three clouds at once without colliding, which is its own surprise.

One more rule people trip on: a subnet cannot attach to an IGW directly. The link runs through the RT.

Subnet  →  RT  →  IGW

Many RTs can point at the same IGW. But the arrow is a routing path, not ownership: the IGW hangs off the VPC, and the RT only points at it.

Two walls, and only one remembers you

A packet heading for your instance passes two filters. They are not the same kind of wall.

SGNACL
Levelthe instance (its ENI)the subnet
Memorystatefulstateless
Rulesallow onlyallow and deny
SourcesCIDR, IP, or another SGCIDR only
Evaluationall rules at oncenumbered 1–32766, first match wins

That is stateful. The SG tracks the connection, not the port. Allow a ping in, and the pong goes back out for free, no reverse rule. That resolves the opening puzzle: inbound 80 alone is enough to serve clients.

The NACL tracks nothing. It judges every packet alone, ping and pong alike, so a working flow needs a rule in both directions.

Caveat: the two-wall split is an AWS quirk. Azure folds both walls into one stateful Network Security Group, attached to a subnet or a NIC. GCP gives you one stateful VPC firewall, targeted by network tag or service account. One stateful wall, no stateless NACL beneath it, so the "open the return direction" chore never shows up.

The path in, and the part everyone forgets

Trace the inbound packet end to end:

IGW  →  VPC picks the subnet  →  NACL  →  SG  →  instance

Here is the part that surprises people: the inbound packet never needs the RT. The IGW drops it straight onto your instance, which lives inside the VPC's address range. The RT speaks up only on the reply, when the destination is some internet IP and 0.0.0.0/0 → IGW finally applies. So the route that makes a subnet public is for the trip out, not the trip in.

Which is why people mix up the RT and the NACL. They do different jobs: the RT decides your next hop, the NACL decides yes or no. One never blocks, the other never routes.

The model to keep

A public subnet is a routing fact, not a switch. The route to the IGW works on the way out, not the way in. The RT picks the next hop, the NACL says yes or no, and only the SG remembers you. Hold those three and the VPC stops being a maze.

References