AWS Networking Basics For Developers

Fullstack developer crafting elegant web solutions with React.js, TypeScript, graphQL, Node.js and AWS
What this blog covers: Building a VPC from scratch, understanding every component, calculating CIDR ranges, and connecting everything together securely.
What is a VPC?
A Virtual Private Cloud (VPC) is your own isolated network inside AWS. Everything you deploy — EC2 instances, RDS databases, Lambda functions — lives inside a VPC.
Key facts:
One VPC per region (but you can have multiple VPCs)
You define its IP address space using CIDR notation
By default it's completely isolated — no internet access until you configure it
Free to create (you pay for resources inside it, not the VPC itself)
CIDR Notation — How IP Ranges Work
CIDR stands for Classless Inter-Domain Routing. It's how you define a block of IP addresses.
The Format
10.0.0.0 / 16
─────────
IP Address Prefix length (how many bits are "fixed")
An IPv4 address is 32 bits total, written as 4 groups of 8 bits (octets):
10 . 0 . 0 . 0
│ │ │ │
8 bits 8 bits 8 bits 8 bits = 32 bits total
The Prefix Length Controls How Many IPs You Get
The prefix /N means the first N bits are fixed (your network). The remaining (32 - N) bits are free (your hosts).
Number of usable IPs = 2^(32 - N)
| CIDR | Fixed bits | Free bits | Total IPs | Usable IPs | Typical use |
|---|---|---|---|---|---|
| /8 | 8 | 24 | 16,777,216 | 16,777,214 | Huge enterprise |
| /16 | 16 | 16 | 65,536 | 65,534 | Large VPC |
| /20 | 20 | 12 | 4,096 | 4,094 | Medium VPC |
| /24 | 24 | 8 | 256 | 254 | Typical subnet |
| /26 | 26 | 6 | 64 | 62 | Small subnet |
| /28 | 28 | 4 | 16 | 14 | Tiny subnet |
| /32 | 32 | 0 | 1 | 1 | Single IP address |
Why "usable IPs" is always 2 less than total: AWS reserves 5 IPs per subnet (first 4 + last 1). So a /24 gives 256 - 5 = 251 usable IPs.
Visual: What /16 vs /24 Looks Like
10.0.0.0/16 → covers 10.0.0.0 to 10.0.255.255
fixed: [10.0] free: [0-255].[0-255]
10.0.1.0/24 → covers 10.0.1.0 to 10.0.1.255
fixed: [10.0.1] free: [0-255]
The 5 Reserved IPs AWS Takes (per subnet)
For a subnet 10.0.1.0/24:
| IP Address | Reserved for |
|---|---|
| 10.0.1.0 | Network address |
| 10.0.1.1 | VPC router |
| 10.0.1.2 | AWS DNS |
| 10.0.1.3 | AWS future use |
| 10.0.1.255 | Broadcast address |
So 251 IPs are actually available for your resources.
How to Calculate IPs for Your VPC and Subnets
Step-by-step planning
Rule #1 — VPC must be bigger than all subnets combined
Rule #2 — Subnet CIDR must be within the VPC CIDR range
Rule #3 — Subnets in the same VPC cannot overlap
Example: Planning a production VPC
Goal: 2 public subnets + 2 private subnets across 2 Availability Zones
Why 10.0.1.0 and not 10.0.0.0 for subnets? Leave 10.0.0.0/24 as a spare block. Start subnets at .1, .2, .3, .4 so your numbering is clean and you have room to add subnets later.
The CIDR Calculation Formula
First IP of subnet: your chosen start address
Last IP of subnet: first IP + (2^free_bits - 1)
Example: 10.0.3.0/24
free bits = 32 - 24 = 8
total IPs = 2^8 = 256
range: 10.0.3.0 → 10.0.3.255
Quick sizing guide
| Need | Use this | IPs available |
|---|---|---|
| VPC (large) | /16 | 65,531 |
| VPC (medium) | /20 | 4,091 |
| Subnet (standard) | /24 | 251 |
| Subnet (medium) | /22 | 1,019 |
| Subnet (small) | /26 | 59 |
| Single resource | /32 | 1 |
Private IP ranges you must use (RFC 1918)
AWS VPCs must use private IP ranges — these are NOT routable on the public internet:
10.0.0.0 – 10.255.255.255 → /8 (16M IPs)
172.16.0.0 – 172.31.255.255 → /12 (1M IPs)
192.168.0.0 – 192.168.255.255 → /16 (65K IPs)
Best practice: Use
10.0.0.0/16for your VPC CIDR. It's the most spacious and clearest for mental mapping.
Step 1 — Create the VPC
What it is
The outer boundary of your private network in AWS. All subnets, gateways, and resources live inside it.
How to create it
AWS Console:
Go to VPC → Your VPCs → Create VPC
Choose "VPC only" (not VPC and more — we want manual control)
Set the IPv4 CIDR block
Configuration:
Name: my-production-vpc
IPv4 CIDR: 10.0.0.0/16
Tenancy: Default (shared hardware — cheaper)
What gets created automatically
A default route table (main route table)
A default security group
A default network ACL
⚠️ Don't use the default VPC for production. It has permissive defaults and a fixed CIDR. Always create a custom VPC.
Step 2 — Create Subnets
What they are
Subnets carve your VPC CIDR into smaller chunks, each tied to a specific Availability Zone. A subnet is "public" only if its route table has a route to an Internet Gateway — not because of its name.
VPC: 10.0.0.0/16
Public Subnet A (10.0.1.0/24) [us-east-1a] ← has route to IGW
Public Subnet B (10.0.2.0/24) [us-east-1b] ← has route to IGW
Private Subnet A (10.0.3.0/24) [us-east-1a] ← no route to IGW
Private Subnet B (10.0.4.0/24) [us-east-1b] ← no route to IGW
How to create them
For each subnet, go to VPC → Subnets → Create Subnet:
Subnet 1 — Public A
Name: public-subnet-a
VPC: my-production-vpc
AZ: us-east-1a
CIDR: 10.0.1.0/24
Subnet 2 — Public B
Name: public-subnet-b
VPC: my-production-vpc
AZ: us-east-1b
CIDR: 10.0.2.0/24
Subnet 3 — Private A
Name: private-subnet-a
VPC: my-production-vpc
AZ: us-east-1a
CIDR: 10.0.3.0/24
Subnet 4 — Private B
Name: private-subnet-b
VPC: my-production-vpc
AZ: us-east-1b
CIDR: 10.0.4.0/24
Enable auto-assign public IP (public subnets only)
For each public subnet: Actions → Edit Subnet Settings → Enable auto-assign public IPv4 address
Why two AZs? If one AZ goes down (rare but it happens), your service keeps running from the other AZ. This is high availability.
Step 3 — Internet Gateway (IGW)
What it is
The door between your VPC and the internet. Without it, nothing in your VPC can reach the outside world — even resources in "public" subnets.
Internet
│
[Internet Gateway] ← attached to VPC
│
[VPC]
How it works
Two-way: allows inbound AND outbound internet traffic
Stateless at the gateway level (Security Groups handle stateful inspection)
Horizontally scalable: AWS manages it — no bandwidth limits, no single point of failure
One per VPC
How to create and attach it
VPC → Internet Gateways → Create Internet Gateway
Name it
my-igwAfter creation: Actions → Attach to VPC → select
my-production-vpc
⚠️ Two-step process! Creating the IGW is not enough. You must explicitly attach it to your VPC. Then you must add a route pointing to it in your public subnet route table.
Step 4 — Route Tables
What they are
A route table is a set of rules that tells traffic "where to go." Every subnet must be associated with exactly one route table.
The two route tables you need
Public Route Table (for public subnets):
Destination Target
10.0.0.0/16 local ← talk to any resource in the VPC
0.0.0.0/0 igw-xxxxx ← everything else → internet
Private Route Table (for private subnets):
Destination Target
10.0.0.0/16 local ← talk to any resource in the VPC
0.0.0.0/0 nat-xxxxx ← everything else → NAT Gateway (outbound only)
How to create them
Create public route table:
VPC → Route Tables → Create Route Table
Name:
public-rt, VPC:my-production-vpcRoutes tab → Edit routes → Add route:
0.0.0.0/0→ Target: Internet Gateway →my-igwSubnet Associations tab → Associate
public-subnet-aandpublic-subnet-b
Create private route table:
Create Route Table:
private-rt, VPC:my-production-vpcAdd route:
0.0.0.0/0→ Target: NAT Gateway (do this after creating the NAT GW)Associate
private-subnet-aandprivate-subnet-b
Route table flow diagram
Incoming request from internet:
0.0.0.0/0 → [IGW] → [Public subnet] → ALB → EC2 in private subnet
EC2 making outbound call:
EC2 → [Private subnet route table] → 0.0.0.0/0 → [NAT GW] → [IGW] → Internet
Response comes back through NAT GW → EC2 (NAT handles translation)
Step 5 — NAT Gateway
What it is
NAT = Network Address Translation. The NAT Gateway gives private subnet resources one-way internet access — they can initiate outbound connections, but the internet cannot initiate connections to them.
Private EC2 (10.0.3.10)
│ wants to reach api.github.com
NAT Gateway (in public subnet, has Elastic IP: 54.x.x.x)
│ translates 10.0.3.10 → 54.x.x.x
Internet Gateway
│
api.github.com
│ response goes back to 54.x.x.x (Elastic IP)
NAT Gateway translates back → delivers to 10.0.3.10
⛔ Someone trying to reach 10.0.3.10 FROM the internet? Blocked.
Where it lives
In the public subnet — it needs internet access itself to forward traffic out.
How to create it
VPC → NAT Gateways → Create NAT Gateway
Subnet:
public-subnet-a(must be public!)Connectivity: Public
Elastic IP: Allocate Elastic IP (click the button)
Create
Then add to your private route table:
0.0.0.0/0 → nat-xxxxxxxx
Cost warning
NAT Gateway costs ~\(0.045/hour + \)0.045/GB data. For dev environments, consider a NAT Instance (cheaper but requires manual management).
Step 6 — Security Groups
What they are
Virtual firewalls attached to individual resources (EC2, RDS, ALB, etc.). They control what traffic is allowed in (inbound) and out (outbound).
Key properties
| Property | Detail |
|---|---|
| Stateful | Allow inbound → response automatically allowed out |
| Allow-only | No deny rules — only allow rules |
| Default | All inbound DENIED, all outbound ALLOWED |
| Scope | Attached per resource, not per subnet |
| Multiple | One resource can have up to 5 security groups |
Stateful explained
Client → [HTTPS port 443, ALLOWED] → EC2
EC2 → [Response traffic] → Client ✅ automatic, no rule needed
Compare with NACL (stateless):
EC2 → [Response on port 52341] → Client ❌ must explicitly allow ephemeral ports
The 3-tier Security Group pattern
Internet
│ (HTTPS 443)
[SG-ALB] → inbound: 443 from 0.0.0.0/0
outbound: 8080 to SG-App
│ (HTTP 8080)
[SG-App] → inbound: 8080 from SG-ALB
outbound: 3306 to SG-DB
│ (MySQL 3306)
[SG-DB] → inbound: 3306 from SG-App
outbound: all (default)
How to create Security Groups
SG-ALB (Load Balancer):
Name: sg-alb
VPC: my-production-vpc
Inbound rules:
Type: HTTPS Port: 443 Source: 0.0.0.0/0 (internet traffic)
Type: HTTP Port: 80 Source: 0.0.0.0/0 (redirect to HTTPS)
Outbound rules:
Type: Custom TCP Port: 8080 Destination: sg-app (to app servers)
SG-App (EC2 App Servers):
Name: sg-app
VPC: my-production-vpc
Inbound rules:
Type: Custom TCP Port: 8080 Source: sg-alb ← reference by SG ID!
Outbound rules:
Type: MySQL/Aurora Port: 3306 Destination: sg-db
Type: HTTPS Port: 443 Destination: 0.0.0.0/0 (for API calls via NAT)
SG-DB (RDS Database):
Name: sg-db
VPC: my-production-vpc
Inbound rules:
Type: MySQL/Aurora Port: 3306 Source: sg-app ← only app servers can reach it!
Outbound rules:
(leave default — all outbound allowed)
Why reference SGs by ID instead of IP?
If you used IP: Source = 10.0.3.15
→ scale to 10 EC2s = update rule 10 times
If you used SG: Source = sg-app
→ any EC2 with sg-app attached = always works
→ scale to 100 EC2s = no changes needed ✅
Step 7 — Network ACLs (Extra Layer)
What they are
Network ACLs (NACLs) are subnet-level firewalls. They're stateless (must allow return traffic explicitly) and support both allow and deny rules.
SG vs NACL comparison
| Feature | Security Group | Network ACL |
|---|---|---|
| Level | Resource (EC2, RDS...) | Subnet |
| State | Stateful | Stateless |
| Rules | Allow only | Allow AND Deny |
| Rule evaluation | All rules at once | In order (lowest wins) |
| Default | Deny all inbound | Allow all in and out |
| Use for | Primary defense | Extra layer / IP blocks |
Private subnet NACL example
Inbound rules:
Rule 100 ALLOW TCP 10.0.0.0/16 all ports ← VPC-internal traffic only
Rule * DENY ALL 0.0.0.0/0 ← block everything else
Outbound rules:
Rule 100 ALLOW TCP 10.0.0.0/16 all ports ← VPC-internal
Rule 200 ALLOW TCP 0.0.0.0/0 1024-65535 ← ephemeral ports for responses
Rule * DENY ALL 0.0.0.0/0 ← block everything else
Ephemeral ports (1024–65535): When a server responds to a client, it uses a random high port. Since NACLs are stateless, you must explicitly allow these return ports in your outbound rules.
Full Architecture — How It All Connects
Traffic flow (inbound request):
Internet → IGW → ALB (public subnet) → EC2 (private subnet) → RDS (private subnet)
Traffic flow (outbound from EC2):
EC2 → Private route table → NAT GW (public subnet) → IGW → Internet
Request lifecycle step by step
1. User hits https://myapp.com
│
2. DNS resolves to ALB's public IP
│
3. Internet → IGW → ALB (SG-ALB allows port 443 from 0.0.0.0/0)
│
4. ALB → EC2 (SG-App allows port 8080 from SG-ALB)
│
5. EC2 → RDS (SG-DB allows port 3306 from SG-App)
│
6. RDS returns data → EC2 → ALB → User
[EC2 calling external API]
7. EC2 → private route table → 0.0.0.0/0 → NAT GW → IGW → api.example.com
8. Response → IGW → NAT GW → EC2 (internet can't initiate step 7 in reverse)
Security Group Cheat Sheet
| Resource | Inbound | Source |
|---|---|---|
| Load Balancer | 443 (HTTPS), 80 (HTTP) | 0.0.0.0/0 |
| EC2 app server | 8080 (or your app port) | SG-ALB |
| RDS MySQL | 3306 | SG-App |
| RDS Postgres | 5432 | SG-App |
| Redis | 6379 | SG-App |
| Bastion host | 22 (SSH) | Your IP only |
| EC2 via Bastion | 22 (SSH) | SG-Bastion |
Common Mistakes
❌ Subnet is "public" because I named it public
✅ A subnet is public ONLY if its route table routes 0.0.0.0/0 to an IGW
❌ I created the IGW but resources still can't reach the internet
✅ Did you: (1) attach IGW to VPC? (2) add the route in the route table?
❌ My private EC2 can't download packages
✅ Check: (1) NAT Gateway exists? (2) private route table has 0.0.0.0/0 → NAT? (3) NAT Gateway is in a PUBLIC subnet?
❌ I set SG inbound source to the app server's IP address
✅ Set source to the SG ID (sg-xxxxxxxx) — it scales automatically
❌ NACL is blocking my traffic even though SG allows it
✅ NACLs are stateless — add outbound rule for ephemeral ports 1024-65535
❌ My subnets overlap (10.0.1.0/24 and 10.0.1.128/24)
✅ AWS will reject this. Plan CIDR ranges before creating subnets.
❌ I used /32 for my VPC CIDR
✅ VPC minimum is /28, maximum is /16. Recommended: /16 or /20.
NAT Gateway MUST live in a public subnet
Here's the core logic: the NAT Gateway's entire job is to forward your private resources' traffic out to the internet. To do that, it needs internet access itself. And to have internet access, it must be in a subnet whose route table points to the Internet Gateway.
That's the definition of a public subnet.
Private EC2 wants to reach github.com
│
Private route table: 0.0.0.0/0 → NAT Gateway
│
NAT Gateway (sitting in PUBLIC subnet, has Elastic IP)
│
Public route table: 0.0.0.0/0 → IGW
│
Internet Gateway → github.com
If you put the NAT Gateway in a private subnet, it would have no route to the internet itself — so it could never forward traffic anywhere. It would be completely useless, like a postal relay office with no road connecting it to the outside world.
What makes the NAT Gateway "safe" even though it's public
People get nervous hearing "NAT Gateway is in the public subnet" — but here's why it's not a security risk:
The NAT Gateway does not have a Security Group. It's not a compute resource you SSH into. It's a managed AWS appliance that only does one thing: translate source IPs. It never accepts unsolicited inbound connections from the internet — it only handles return traffic for connections that your private resources initiated.
The protection of your private EC2 and RDS instances comes from the fact that they have no public IP and no route from the internet reaching them. The NAT Gateway is just a forwarding pipe — it's not the thing being protected.
Internet trying to reach your private EC2:
Internet → IGW → ... no route to 10.0.3.x exists in public subnet → DROPPED
Your private EC2 calling the internet:
EC2 → NAT GW → IGW → Internet → response back to NAT GW → EC2 ✅
One more practical tip
You should create one NAT Gateway per Availability Zone, not one total. If you have subnets in us-east-1a and us-east-1b, create a NAT Gateway in each public subnet, then point each private subnet's route table to the NAT Gateway in the same AZ. If you share a single NAT Gateway across AZs and that AZ goes down, all your private instances lose internet access simultaneously — which defeats the whole purpose of multi-AZ architecture.



