AWS Builder Center
AWS VPC(Virtual Private Cloud) Explained Like a Gated Society: Gates, Guards, Roads, and a 15-Minute Lab

AWS VPC(Virtual Private Cloud) Explained Like a Gated Society: Gates, Guards, Roads, and a 15-Minute Lab

A plain-English but technical breakdown of AWS VPC: subnets, route tables, gateways, security groups vs NACLs, and endpoints. Includes a hands-on CLI lab, an attacker vs defender view, and a checklist of the mistakes that get accounts breached.

Cybersecurity Enthusiast | Aspiring Cloud Security Engineer
TL;DR: A VPC is your private slice of AWS. "Public" or "private" is decided by one route, not a checkbox. Security groups guard instances, NACLs guard subnets, endpoints keep AWS traffic off the internet, and Flow Logs tell you what happened. Read the story, run the lab, steal the checklist.

Start here: a 10-second quiz

You launch an EC2 instance in a "private" subnet. What actually makes the subnet private?
A) The instance has no public IP B) The subnet is named "private" C) Its route table has no route to an Internet Gateway D) The security group blocks everything
If you picked C, you already understand more VPC than plenty of people who've used AWS for years. (A and D help, but they don't define it. B is just a label.)
Here's what you'll get from this article:
  • βœ… A mental model you won't forget (gated housing society)
  • βœ… The journey of one request, from the internet to your server and back
  • βœ… Security Groups vs NACLs, finally clear
  • βœ… A 15-minute CLI lab with proof and cleanup
  • βœ… Attacker vs defender view, common mistakes, and a checklist

🏘️ The mental model: a gated society

Forget diagrams for a minute. A VPC is a gated housing society that only you own.
Real worldAWSWhat it does
The whole societyVPCYour private network with its own IP range
Blocks / wingsSubnetsSlices of the society, each in one Availability Zone
Roads and signboardsRoute tablesDecide where traffic goes next
Main gate to the cityInternet Gateway (IGW)Two-way door to the internet
One-way exit gateNAT GatewayPrivate things go out, nothing comes in
Guard at each flat doorSecurity GroupFirewall at the instance
Guard at each block entranceNetwork ACL (NACL)Firewall at the subnet
Private tunnel to a nearby shopVPC EndpointReach AWS services without the public internet
CCTVVPC Flow LogsRecords who talked to whom

πŸ—ΊοΈ The big picture

 a Region containing a VPC with two Availability Zones, each with a public subnet routed to an Internet Gateway and a private subnet with no internet route

Three paths to remember:

  • Inbound: Internet β†’ IGW β†’ Load Balancer β†’ App β†’ Database
  • Outbound (updates, APIs): App β†’ NAT Gateway β†’ IGW β†’ Internet
  • AWS services: App β†’ S3 Gateway Endpoint β†’ S3 (never touches the internet)

πŸ“¦ Follow one request

You open a website hosted in this VPC. Here's what the packet goes through:
  1. DNS resolves the domain to the load balancer's public IP
  2. The packet hits the Internet Gateway
  3. The subnet's route table sends it to the load balancer inside the VPC
  4. The subnet's NACL inbound rules are checked
  5. The load balancer's Security Group is checked
  6. The load balancer forwards to your app. The app's Security Group must allow traffic from the load balancer's Security Group
  7. The response goes back. Security Groups allow it automatically (stateful), but the NACL outbound rules must allow it too, or it silently dies
Step 7 is the classic "why does it work one way only?" bug. We'll come back to it.

🧩 Piece by piece

1. The VPC and its CIDR

Your VPC gets an IP range like 10.0.0.0/16 (about 65,000 addresses). Pick it before you build, because changing it later hurts.
  • Don't overlap with your office network, home lab, or any VPC you might peer with later. Overlapping ranges can't be connected
  • A comfortable default: /16 for the VPC, /24 for subnets
  • A /24 gives you 251 usable IPs, because AWS reserves 5 per subnet

2. Subnets: "public" is not a setting

There's no "make this public" checkbox. A subnet is public only because its route table sends 0.0.0.0/0 to an Internet Gateway. Delete that route and it's private.
A subnet lives in one Availability Zone. For resilience, create one per AZ.

3. Route tables: the real boss

Every subnet follows exactly one route table. Two rules:
  • The most specific route wins (10.0.0.0/16 beats 0.0.0.0/0)
  • Every VPC has a built-in local route, so anything in the VPC can reach anything else in the VPC. Security Groups are what stop that

4. IGW vs NAT Gateway

Internet GatewayNAT Gateway
DirectionTwo-wayOutbound only
Used byResources with public IPsPrivate instances that need updates or APIs
Lives inAttached to the VPCA public subnet
CostNo hourly chargeHourly charge plus data processing
NAT Gateways are the classic "why is my bill high?" surprise. Never leave one running after a lab.

5. Security Groups vs NACLs

The interview question that never dies:
Security GroupNetwork ACL
LevelInstance / network interfaceSubnet
StateStateful: return traffic is automaticStateless: allow both directions yourself
RulesAllow onlyAllow and deny
EvaluationAll rules togetherIn number order, first match wins
DefaultNew SG: deny inbound, allow outboundDefault NACL allows all. A new custom NACL denies all
Practical rule: do your real work in Security Groups. Use NACLs as a blunt backup, for example to deny a known-bad IP range for a whole subnet.

6. VPC Endpoints: keep AWS traffic off the internet

Without an endpoint, a private instance reaches S3 by going out through NAT to the public S3 endpoint. That costs money and widens your exposure.
  • Gateway endpoints (S3 and DynamoDB): added as a route, no hourly charge
  • Interface endpoints (PrivateLink, most other services): a network interface in your subnet, billed hourly plus data
Attach an endpoint policy to limit what can be reached through it.

πŸ—οΈ Real-world layout: the 3-tier pattern

This is how most production apps are laid out. The trick is Security Groups that point at other Security Groups, not at IP ranges.
TierSubnetInbound allowed from
Load balancerPublic443 from 0.0.0.0/0
App serversPrivate8080 from the load balancer's SG
DatabasePrivate5432 from the app's SG
1
2
3
# Database SG: only the app tier can talk to Postgres
aws ec2 authorize-security-group-ingress --group-id $DB_SG \
--protocol tcp --port 5432 --source-group $APP_SG
Servers come and go and their IPs change. The rule "app tier can reach the DB" stays true.

πŸ§ͺ Lab: build it yourself in about 15 minutes

Use a test account. Everything below is free to create: VPCs, subnets, IGWs, route tables, security groups and gateway endpoints have no hourly charge. We skip the NAT Gateway on purpose.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
export AWS_DEFAULT_REGION=ap-south-1
AZ=${AWS_DEFAULT_REGION}a

# 1. VPC
VPC=$(aws ec2 create-vpc --cidr-block 10.0.0.0/16 \
--query Vpc.VpcId --output text)
aws ec2 create-tags --resources $VPC --tags Key=Name,Value=lab-vpc
aws ec2 modify-vpc-attribute --vpc-id $VPC --enable-dns-hostnames '{"Value":true}'

# 2. Two subnets in the same AZ
PUB=$(aws ec2 create-subnet --vpc-id $VPC --cidr-block 10.0.1.0/24 \
--availability-zone $AZ --query Subnet.SubnetId --output text)
PRIV=$(aws ec2 create-subnet --vpc-id $VPC --cidr-block 10.0.2.0/24 \
--availability-zone $AZ --query Subnet.SubnetId --output text)

# 3. Internet Gateway
IGW=$(aws ec2 create-internet-gateway \
--query InternetGateway.InternetGatewayId --output text)
aws ec2 attach-internet-gateway --internet-gateway-id $IGW --vpc-id $VPC

# 4. Route table with an IGW route, attached to the PUBLIC subnet only
RT=$(aws ec2 create-route-table --vpc-id $VPC \
--query RouteTable.RouteTableId --output text)
aws ec2 create-route --route-table-id $RT \
--destination-cidr-block 0.0.0.0/0 --gateway-id $IGW
aws ec2 associate-route-table --route-table-id $RT --subnet-id $PUB

πŸ” Prove the "public vs private" claim

1
2
3
4
5
6
7
8
9
10
11
# Public subnet: expect a 0.0.0.0/0 route pointing at igw-...
aws ec2 describe-route-tables \
--filters Name=association.subnet-id,Values=$PUB \
--query 'RouteTables[].Routes[].[DestinationCidrBlock,GatewayId]' --output table

# Private subnet: no explicit route table, so it uses the MAIN one. No IGW route
MAIN=$(aws ec2 describe-route-tables \
--filters Name=vpc-id,Values=$VPC Name=association.main,Values=true \
--query 'RouteTables[0].RouteTableId' --output text)
aws ec2 describe-route-tables --route-table-ids $MAIN \
--query 'RouteTables[].Routes[].[DestinationCidrBlock,GatewayId]' --output table
Same VPC, same account. The only difference is one route. That's the whole "public subnet" secret.

πŸ”’ Add a tight Security Group and a private path to S3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
SG=$(aws ec2 create-security-group --group-name lab-web \
--description "lab web tier" --vpc-id $VPC \
--query GroupId --output text)
aws ec2 authorize-security-group-ingress --group-id $SG \
--protocol tcp --port 443 --cidr 0.0.0.0/0

# Gateway endpoint for S3, attached to the private side (main route table)
VPCE=$(aws ec2 create-vpc-endpoint --vpc-id $VPC \
--service-name com.amazonaws.${AWS_DEFAULT_REGION}.s3 \
--route-table-ids $MAIN \
--query VpcEndpoint.VpcEndpointId --output text)

# See the new S3 route. Endpoint routes use a prefix list, not a CIDR
aws ec2 describe-route-tables --route-table-ids $MAIN \
--query 'RouteTables[].Routes[].[DestinationCidrBlock,DestinationPrefixListId,GatewayId]' \
--output table
You should now see a pl-... entry pointing at a vpce-... target. That's your private S3 path.

🧹 Cleanup

1
2
3
4
5
6
7
8
aws ec2 delete-vpc-endpoints --vpc-endpoint-ids $VPCE
aws ec2 delete-subnet --subnet-id $PUB
aws ec2 delete-subnet --subnet-id $PRIV
aws ec2 delete-route-table --route-table-id $RT
aws ec2 detach-internet-gateway --internet-gateway-id $IGW --vpc-id $VPC
aws ec2 delete-internet-gateway --internet-gateway-id $IGW
aws ec2 delete-security-group --group-id $SG
aws ec2 delete-vpc --vpc-id $VPC
If a delete fails, something still depends on that resource. Read the error and work backwards.

πŸ”΄ Attacker view vs πŸ”΅ Defender view

What the attacker doesWhat the defender does
Scans for Security Groups open to the world on SSH (22) or RDP (3389)Never expose admin ports to 0.0.0.0/0. Use SSM Session Manager
Finds an SSRF bug and queries the instance metadata service for role credentialsEnforce IMDSv2 and keep instance roles least-privilege
Lands on one instance and moves sideways through loose Security GroupsTier your SGs and reference SGs instead of CIDR ranges
Exfiltrates data out through NAT to their own serverRestrict egress, use endpoint policies, watch Flow Logs
Hunts the default VPC, where subnets are public by defaultKeep real workloads out of the default VPC
For detection, turn on VPC Flow Logs and pair them with GuardDuty. Flow Logs show who talked to whom, GuardDuty flags the weird stuff.

❌ Mistakes that actually cause incidents

  • Database in a public subnet "just for now"
  • 0.0.0.0/0 on port 22 or 3389
  • Running production in the default VPC
  • Overlapping CIDR ranges, discovered only when you try to peer
  • A NAT Gateway left running after a lab
  • Using NACLs as the main firewall, then losing a day to stateless return-traffic rules
  • No Flow Logs, so nobody can answer "what happened?"

βœ… Your 8-point VPC checklist

  • [ ] CIDR planned so it won't overlap with anything you'll connect
  • [ ] Subnets spread across at least two AZs
  • [ ] Databases and app servers in private subnets
  • [ ] Only the load balancer lives in the public tier
  • [ ] Security Groups reference other Security Groups
  • [ ] Gateway endpoints for S3 and DynamoDB
  • [ ] Flow Logs enabled
  • [ ] Nothing important in the default VPC

πŸ“Œ Cheat sheet: "I need X, so I use Y"

I need to...Use
Put a web app on the internetPublic subnet + IGW + load balancer
Let a private server download updatesNAT Gateway
Let a private server reach S3 or DynamoDBGateway endpoint
Let a private server reach other AWS APIs privatelyInterface endpoint (PrivateLink)
Allow tier-to-tier trafficSecurity Group referencing a Security Group
Block one bad IP range from a whole subnetNACL deny rule
Log in to private instancesSSM Session Manager
See who talked to whomVPC Flow Logs

πŸ’¬ Your turn

  1. Did the route-table check make "public vs private" click? Tell me what you saw in your output
  2. What confused you most: route tables, SG vs NACL, or NAT vs IGW? Vote, and the winner gets a deep-dive
  3. Drop your own VPC gotcha in the comments. We all learn more from the thing that broke
Next in this series: VPC Peering vs Transit Gateway, and how to read VPC Flow Logs like an investigator.
Found it useful? Save it for your next AWS interview. πŸ”–
Any opinions in this article are those of the individual author and may not reflect the opinions of AWS.
Enjoyed reading this content? Let the author know!

Your likes, comments, shares, and saves help creators reach more builders.

Loading recommendations

Loading article