Skip to main content

Command Palette

Search for a command to run...

AWS Networking Basics For Developers

Published
16 min read
AWS Networking Basics For Developers
O

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/16 for 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:

  1. Go to VPC → Your VPCs → Create VPC

  2. Choose "VPC only" (not VPC and more — we want manual control)

  3. 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

  1. VPC → Internet Gateways → Create Internet Gateway

  2. Name it my-igw

  3. After 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:

  1. VPC → Route Tables → Create Route Table

  2. Name: public-rt, VPC: my-production-vpc

  3. Routes tab → Edit routes → Add route: 0.0.0.0/0 → Target: Internet Gateway → my-igw

  4. Subnet Associations tab → Associate public-subnet-a and public-subnet-b

Create private route table:

  1. Create Route Table: private-rt, VPC: my-production-vpc

  2. Add route: 0.0.0.0/0 → Target: NAT Gateway (do this after creating the NAT GW)

  3. Associate private-subnet-a and private-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

  1. VPC → NAT Gateways → Create NAT Gateway

  2. Subnet: public-subnet-a (must be public!)

  3. Connectivity: Public

  4. Elastic IP: Allocate Elastic IP (click the button)

  5. 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.

More from this blog

C

Code & Cloud with Othmane

8 posts

Welcome to my corner of the tech universe! I'm Othmane, a passionate explorer of cloud computing and web development. This blog is my digital notebook where i share what i learned.