A packet's walk through a VPC, in AWS
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
RTand oneNACL. - 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.
SG | NACL | |
|---|---|---|
| Level | the instance (its ENI) | the subnet |
| Memory | stateful | stateless |
| Rules | allow only | allow and deny |
| Sources | CIDR, IP, or another SG | CIDR only |
| Evaluation | all rules at once | numbered 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
NACLbeneath 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
- Azure network security groups overview — Microsoft Learn. NSG is stateful; attaches to a subnet or a NIC.
- VPC firewall rules — Google Cloud. Firewall rules are stateful; targeted by network tag or service account.