Skip to content

Lab 1 · Build a Custom VPC

Security Networking · Est. time: 40–50 min · Cost: ⚠️ NOT fully Free Tier — the NAT Gateway bills while it exists (see below). Tear down at the end.

Goal. Build a VPC from scratch — public and private subnets across two AZs, an internet gateway, a NAT gateway, and route tables — and see the traffic-flow rules the exam tests (Topics 3 & 10).

Money warning — read first

  • A NAT Gateway is not Free Tier: it bills \$0.045/hr plus \$0.045/GB processed (us-east rates) the entire time it exists — roughly \$1+/day even idle.
  • Since Feb 2024, every public IPv4 address bills \$0.005/hr (in use or not), including the Elastic IP on the NAT Gateway.
  • Do the teardown the same day. Set a budget alarm first.
  • Exact prices vary by Region and change — confirm on the VPC pricing page.

What you'll build

VPC 10.0.0.0/16
├── Public subnet  10.0.1.0/24 (AZ-a)  → route 0.0.0.0/0 → Internet Gateway
├── Public subnet  10.0.2.0/24 (AZ-b)  → route 0.0.0.0/0 → Internet Gateway
├── Private subnet 10.0.11.0/24 (AZ-a) → route 0.0.0.0/0 → NAT Gateway
└── Private subnet 10.0.12.0/24 (AZ-b) → route 0.0.0.0/0 → NAT Gateway

The defining difference: a public subnet routes 0.0.0.0/0 to an Internet Gateway; a private subnet routes it to a NAT Gateway (outbound only). That routing — not the subnet's name — is what makes it "public."


Step 1 — Create the VPC and subnets

  1. VPC consoleCreate VPC → choose VPC and more (this wizard builds subnets, route tables, and gateways together).
  2. Name lab-vpc, IPv4 CIDR 10.0.0.0/16.
  3. Set 2 Availability Zones, 2 public and 2 private subnets.
  4. NAT gateways: choose In 1 AZ (cheaper than one-per-AZ for a lab).
  5. VPC endpoints: None for now.
  6. Create VPC. The wizard wires up the IGW, NAT gateway, and route tables.

(Prefer to see each piece? Use the CLI tab, which creates them one at a time.)

# VPC
VPC_ID=$(aws ec2 create-vpc --cidr-block 10.0.0.0/16 \
  --query Vpc.VpcId --output text)
aws ec2 create-tags --resources $VPC_ID --tags Key=Name,Value=lab-vpc

# Subnets (public a/b, private a/b) — swap AZ names for your Region
PUB_A=$(aws ec2 create-subnet --vpc-id $VPC_ID --cidr-block 10.0.1.0/24  --availability-zone us-east-1a --query Subnet.SubnetId --output text)
PUB_B=$(aws ec2 create-subnet --vpc-id $VPC_ID --cidr-block 10.0.2.0/24  --availability-zone us-east-1b --query Subnet.SubnetId --output text)
PRIV_A=$(aws ec2 create-subnet --vpc-id $VPC_ID --cidr-block 10.0.11.0/24 --availability-zone us-east-1a --query Subnet.SubnetId --output text)
PRIV_B=$(aws ec2 create-subnet --vpc-id $VPC_ID --cidr-block 10.0.12.0/24 --availability-zone us-east-1b --query Subnet.SubnetId --output text)

Step 2 — Internet Gateway (makes public subnets public)

Done by the wizard. To inspect: Internet gateways → confirm one is attached to lab-vpc, and the public route table has a route 0.0.0.0/0 → igw-....

IGW_ID=$(aws ec2 create-internet-gateway --query InternetGateway.InternetGatewayId --output text)
aws ec2 attach-internet-gateway --internet-gateway-id $IGW_ID --vpc-id $VPC_ID

# Public route table → IGW, associate the public subnets
RT_PUB=$(aws ec2 create-route-table --vpc-id $VPC_ID --query RouteTable.RouteTableId --output text)
aws ec2 create-route --route-table-id $RT_PUB --destination-cidr-block 0.0.0.0/0 --gateway-id $IGW_ID
aws ec2 associate-route-table --route-table-id $RT_PUB --subnet-id $PUB_A
aws ec2 associate-route-table --route-table-id $RT_PUB --subnet-id $PUB_B

# Auto-assign public IPs in public subnets
aws ec2 modify-subnet-attribute --subnet-id $PUB_A --map-public-ip-on-launch
aws ec2 modify-subnet-attribute --subnet-id $PUB_B --map-public-ip-on-launch

Step 3 — NAT Gateway (private subnets get outbound-only internet)

This is the part that costs money

The NAT Gateway starts billing the moment it's created. Note the time; you'll delete it in teardown.

Created by the wizard in one AZ. Confirm the private route table has 0.0.0.0/0 → nat-....

# Elastic IP + NAT Gateway in a public subnet
EIP_ALLOC=$(aws ec2 allocate-address --domain vpc --query AllocationId --output text)
NAT_ID=$(aws ec2 create-nat-gateway --subnet-id $PUB_A --allocation-id $EIP_ALLOC \
  --query NatGateway.NatGatewayId --output text)
aws ec2 wait nat-gateway-available --nat-gateway-ids $NAT_ID

# Private route table → NAT, associate the private subnets
RT_PRIV=$(aws ec2 create-route-table --vpc-id $VPC_ID --query RouteTable.RouteTableId --output text)
aws ec2 create-route --route-table-id $RT_PRIV --destination-cidr-block 0.0.0.0/0 --nat-gateway-id $NAT_ID
aws ec2 associate-route-table --route-table-id $RT_PRIV --subnet-id $PRIV_A
aws ec2 associate-route-table --route-table-id $RT_PRIV --subnet-id $PRIV_B

Step 4 — Observe the traffic-flow rules (optional, Free-Tier EC2)

To feel public vs private:

  1. Launch a t2.micro/t3.micro in a public subnet (auto-assigns a public IP). Connect via EC2 Instance Connect — it works because of the IGW route.
  2. Launch one in a private subnet (no public IP). You can't reach it directly from the internet, but from inside it curl https://example.com works — outbound via the NAT Gateway.
  3. Note: the private instance can reach out, but nothing on the internet can start a connection to it. That's the NAT-vs-IGW distinction the exam tests.

Terminate both instances before teardown.


Teardown (do this today)

Order matters (delete the biller first):

  1. Terminate any EC2 instances you launched.
  2. Delete the NAT Gateway (VPC → NAT gateways) — this stops the hourly bill. Wait for it to fully delete.
  3. Release the Elastic IP (VPC → Elastic IPs) so it stops billing.
  4. Delete the VPC (VPC → Your VPCs → Delete) — this removes subnets, route tables, and the IGW with it.
aws ec2 delete-nat-gateway --nat-gateway-id $NAT_ID
aws ec2 wait nat-gateway-deleted --nat-gateway-ids $NAT_ID
aws ec2 release-address --allocation-id $EIP_ALLOC
# (detach/delete IGW, delete subnets/route tables, then the VPC — or just:)
aws ec2 delete-vpc --vpc-id $VPC_ID   # after dependencies are gone

Confirm zero cost

After teardown, check there are no NAT Gateways and no unassociated Elastic IPs in the Region. That's where surprise VPC bills come from.


✅ What you should understand now

  • A subnet is public because its route table sends 0.0.0.0/0 to an Internet Gateway — not because of its name.
  • A NAT Gateway gives private-subnet resources outbound-only internet; the internet can't initiate connections inward. An IGW allows both directions (for resources with public IPs).
  • NAT Gateways and public IPv4 addresses cost money continuously — the #1 source of surprise lab bills. Always tear them down.
  • Spreading subnets across two AZs is the foundation of the resilient designs in Topic 4.
  • To reach S3/DynamoDB privately without a NAT Gateway, you'd add a VPC Gateway endpoint (free) — cheaper and more secure for that traffic.

Related: Learn — Network Security · Learn — Networking Performance