---
title: "03 — VPC: Drawing My Own Network in AWS"
uid: vpc-networking
tags: ["aws", "cidr", "networking", "roadmap:aws", "security-groups", "nat", "vpc", "subnet"]
excerpt: "A VPC is just a network you draw: an IP range carved into subnets, route tables deciding where packets go, security groups as per-instance firewalls. The wizard hides the drawing."
date: 2026-08-13T03:28:31+0000
source: https://www.aveshina.my.id/en/blog/vpc-networking
---

"The boring network bit you click through at setup" was how I filed the VPC, until a routing mistake cost me a night of debugging. The model that stuck: **a VPC is a network I draw. I pick an IP range, carve it into subnets, wire up route tables to decide where packets go, and hang firewalls on the edges** [1]. Once I saw it as a sketch rather than a wizard, the public/private subnet distinction, the NAT gateway, and the security group all snapped into place as just parts of the drawing.

## The VPC: a logically isolated network

A VPC — Virtual Private Cloud — is a private network I define inside AWS, into which I launch resources [1]. "Isolated" is doing real work: by default, nothing in my VPC is reachable from the internet unless I explicitly open a path. The default VPC AWS hands new accounts is wide open, which is convenient for tutorials and terrible for production — so the first real architectural act is almost always building a custom VPC from scratch.

The drawing has one big boundary first: a **CIDR block** — the total IP range the VPC can use.

## CIDR blocks: the address budget

CIDR — Classless Inter-Domain Routing — is just a compact way to write a range of IP addresses [2]. 10.0.0.0/16 means "everything from 10.0.0.0 to 10.0.255.255," which is 65,536 addresses. /28 is the smallest AWS allows (16 addresses); /16 is the largest for a VPC. The number after the slash is how many bits are fixed — bigger slash, fewer addresses.

Two rules I treat as law:

- **Pick a non-overlapping range.** The VPC's CIDR must not collide with my office network, a peered VPC, or an on-prem datacenter I might later connect. Overlap is a painful, late-discovered bug.
- **The CIDR cannot be resized after creation.** I can add *secondary* CIDRs later, but the primary is permanent. So I leave headroom: a /16 is usually a safe default even when I think I need a /24.

The shorthand that makes CIDR readable: /24 ≈ 256 addresses, /25 ≈ 128, /26 ≈ 64, /27 ≈ 32, /28 ≈ 16. Each bit halves the pool.

## Subnets: carving up the budget

A subnet is a slice of the VPC's CIDR, and each one lives in **exactly one Availability Zone** [3]. The thing that finally made subnets click: *public versus private is not a subnet property — it's a route-table property.* A subnet becomes "public" because the route table attached to it points at the Internet Gateway; it becomes "private" because it does not.

- **Public subnet** — its route table sends 0.0.0.0/0 to the Internet Gateway, so instances in it can reach and be reached from the internet (subject to security groups) [5].
- **Private subnet** — no route to the Internet Gateway. Instances here have no direct internet path, which is exactly what I want for databases and internal services.

AWS reserves the first four and last IP of every subnet (network, VPC router, DNS, broadcast), so a /28 gives me only 11 usable addresses. Subnet sizing is less generous than the raw CIDR suggests.

## Route tables: the routing logic

A **route table** is the set of rules that says "traffic destined for X goes to Y" [4]. Each subnet is associated with exactly one route table. The main rules I keep drawing:

- **Local route** — every route table has an automatic entry sending VPC-internal traffic (10.0.0.0/16, say) to local. This is why any two instances in the VPC can talk regardless of subnet.
- **Internet Gateway route** — 0.0.0.0/0 → igw-xxx. Adding this makes the subnet public.
- **NAT Gateway route** — 0.0.0.0/0 → nat-xxx. Used in private subnet tables so instances can reach out without being reachable.

The mental shortcut: the route table *is* the subnet's posture toward the internet.

## Internet Gateway and NAT Gateway

These two confused me until I separated their jobs.

An **Internet Gateway (IGW)** is the VPC's bi-directional door to the public internet [7]. It does NAT for instances that have a public IP, and it's what lets inbound traffic reach a public-subnet instance. Without an IGW attached to the VPC, nothing in the VPC touches the internet, full stop.

A **NAT Gateway** is a managed, single-AZ appliance that gives **private-subnet** instances outbound-only internet access [8]. They can pull patches, reach external APIs, download packages — but nothing outside can initiate a connection to them. NAT is one-way by design.

```figure
<svg viewBox="0 0 700 320" xmlns="http://www.w3.org/2000/svg" class="my-6 w-full max-w-2xl" role="img" aria-label="A VPC with two AZs. The VPC boundary is labelled 10.0.0.0/16. AZ1 (left) has a public subnet 10.0.1.0/24 with an EC2 instance and an Internet Gateway at the top, and a private subnet 10.0.2.0/24 with a database. AZ2 (right) mirrors the layout. A NAT Gateway sits in each public subnet. Arrows: private subnet traffic for the internet is routed up to the NAT Gateway in the public subnet, which forwards through the Internet Gateway.">
  <g font-family="ui-sans-serif, system-ui, sans-serif" text-rendering="geometricPrecision">
    <!-- VPC boundary -->
    <rect x="20" y="40" width="660" height="260" rx="12" fill="none" stroke="#6366f1" stroke-width="2" stroke-dasharray="6 4"/>
    <text x="40" y="32" font-size="12" font-weight="700" fill="#1e1b4b">VPC  10.0.0.0/16</text>

    <!-- Internet (above VPC) -->
    <rect x="300" y="8" width="100" height="26" rx="8" fill="#fce7f3" stroke="#db2777"/>
    <text x="350" y="25" font-size="11" font-weight="700" fill="#500724" text-anchor="middle">Internet</text>

    <!-- IGW -->
    <rect x="300" y="50" width="100" height="26" rx="8" fill="#e0e7ff" stroke="#6366f1"/>
    <text x="350" y="67" font-size="10" font-weight="700" fill="#1e1b4b" text-anchor="middle">Internet Gateway</text>
    <line x1="350" y1="34" x2="350" y2="50" stroke="#64748b" stroke-width="1.5"/>

    <!-- AZ1 -->
    <rect x="40" y="90" width="290" height="200" rx="10" fill="#f8fafc" stroke="#94a3b8" stroke-dasharray="3 3"/>
    <text x="55" y="106" font-size="10" font-weight="700" fill="#475569">AZ1</text>

    <!-- AZ1 public subnet -->
    <rect x="55" y="115" width="260" height="70" rx="6" fill="#dcfce7" stroke="#16a34a"/>
    <text x="65" y="130" font-size="10" font-weight="700" fill="#052e16">public subnet 10.0.1.0/24</text>
    <rect x="70" y="140" width="80" height="36" rx="5" fill="#fff" stroke="#16a34a"/><text x="110" y="162" font-size="9" fill="#052e16" text-anchor="middle">EC2 (web)</text>
    <rect x="190" y="140" width="100" height="36" rx="5" fill="#fef9c3" stroke="#ca8a04"/><text x="240" y="162" font-size="9" fill="#422006" text-anchor="middle">NAT Gateway</text>

    <!-- AZ1 private subnet -->
    <rect x="55" y="200" width="260" height="80" rx="6" fill="#fee2e2" stroke="#dc2626"/>
    <text x="65" y="215" font-size="10" font-weight="700" fill="#7f1d1d">private subnet 10.0.2.0/24</text>
    <rect x="120" y="228" width="120" height="40" rx="5" fill="#fff" stroke="#dc2626"/><text x="180" y="252" font-size="9" fill="#7f1d1d" text-anchor="middle">Database</text>

    <!-- arrow private -> NAT -->
    <path d="M180,200 L180,182 L220,182 L220,176" fill="none" stroke="#64748b" stroke-width="1.2" marker-end="url(#narrow)"/>
    <defs><marker id="narrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto"><path d="M0,0 L10,5 L0,10 z" fill="#64748b"/></marker></defs>

    <!-- AZ2 (mirrored, condensed) -->
    <rect x="370" y="90" width="290" height="200" rx="10" fill="#f8fafc" stroke="#94a3b8" stroke-dasharray="3 3"/>
    <text x="385" y="106" font-size="10" font-weight="700" fill="#475569">AZ2</text>
    <rect x="385" y="115" width="260" height="70" rx="6" fill="#dcfce7" stroke="#16a34a"/>
    <text x="395" y="130" font-size="10" font-weight="700" fill="#052e16">public subnet 10.0.11.0/24</text>
    <rect x="400" y="140" width="80" height="36" rx="5" fill="#fff" stroke="#16a34a"/><text x="440" y="162" font-size="9" fill="#052e16" text-anchor="middle">EC2 (web)</text>
    <rect x="520" y="140" width="100" height="36" rx="5" fill="#fef9c3" stroke="#ca8a04"/><text x="570" y="162" font-size="9" fill="#422006" text-anchor="middle">NAT Gateway</text>
    <rect x="385" y="200" width="260" height="80" rx="6" fill="#fee2e2" stroke="#dc2626"/>
    <text x="395" y="215" font-size="10" font-weight="700" fill="#7f1d1d">private subnet 10.0.12.0/24</text>
    <rect x="450" y="228" width="120" height="40" rx="5" fill="#fff" stroke="#dc2626"/><text x="510" y="252" font-size="9" fill="#7f1d1d" text-anchor="middle">Database</text>
  </g>
</svg>
```

The pattern I draw every time: public subnets on top, private below, NAT in the public subnet, traffic from private goes *up* through NAT and out the IGW. That's a two-tier architecture in one sketch.

## Security groups: stateful per-instance firewalls

A **security group** is a virtual firewall attached to an instance's network interface [6]. The details that distinguish it from a traditional firewall:

- **Stateful.** If I allow outbound on port 443, the reply traffic is allowed back in automatically. I never write return rules.
- **Allow rules only.** I cannot write "deny this IP"; I can only whitelist. The default security group denies all inbound and allows all outbound.
- **Per-instance.** I attach up to five security groups to each instance, and they cumulatively allow.

The cross-subnet implication trips people up: if instance A in subnet 1 needs to talk to instance B in subnet 2, I must allow it on B's *inbound* security group (the route table's local route already handles the path). The security group is the gate; the route table is the road.

## How I use this

VPC design is now a sketching exercise before any clicking. I draw the VPC boundary, drop in at least two AZs, put public-facing things (load balancers, bastions) in public subnets, everything else in private, and give each private subnet a route to a NAT Gateway in a public subnet of the same AZ (NAT is AZ-scoped — cross-AZ NAT works but pays for cross-AZ data transfer). Security groups are locked to least-privilege, and "0.0.0.0/0 on port 22" is treated as a smell. The drawing, not the console, is where the architecture lives.

## References

[1] Amazon Web Services, "Amazon Virtual Private Cloud," 2024. [Online]. Available: [https://aws.amazon.com/vpc/](https://aws.amazon.com/vpc/)

[2] Amazon Web Services, "VPC CIDR blocks," VPC User Guide, 2024. [Online]. Available: [https://docs.aws.amazon.com/vpc/latest/userguide/vpc-cidr-blocks.html](https://docs.aws.amazon.com/vpc/latest/userguide/vpc-cidr-blocks.html)

[3] Amazon Web Services, "Subnets for your VPC," VPC User Guide, 2024. [Online]. Available: [https://docs.aws.amazon.com/vpc/latest/userguide/configure-subnets.html](https://docs.aws.amazon.com/vpc/latest/userguide/configure-subnets.html)

[4] Amazon Web Services, "Route tables for your VPC," VPC User Guide, 2024. [Online]. Available: [https://docs.aws.amazon.com/vpc/latest/userguide/VPC_Route_Tables.html](https://docs.aws.amazon.com/vpc/latest/userguide/VPC_Route_Tables.html)

[5] Amazon Web Services, "Public subnet," VPC User Guide, 2024. [Online]. Available: [https://docs.aws.amazon.com/vpc/latest/userguide/configure-subnets.html](https://docs.aws.amazon.com/vpc/latest/userguide/configure-subnets.html)

[6] Amazon Web Services, "Security groups for your VPC," VPC User Guide, 2024. [Online]. Available: [https://docs.aws.amazon.com/vpc/latest/userguide/vpc-security-groups.html](https://docs.aws.amazon.com/vpc/latest/userguide/vpc-security-groups.html)

[7] Amazon Web Services, "Internet gateways for your VPC," VPC User Guide, 2024. [Online]. Available: [https://docs.aws.amazon.com/vpc/latest/userguide/VPC_Internet_Gateway.html](https://docs.aws.amazon.com/vpc/latest/userguide/VPC_Internet_Gateway.html)

[8] Amazon Web Services, "NAT gateways," VPC User Guide, 2024. [Online]. Available: [https://docs.aws.amazon.com/vpc/latest/userguide/vpc-nat-gateway.html](https://docs.aws.amazon.com/vpc/latest/userguide/vpc-nat-gateway.html)

```quiz
Q: What makes a subnet "public"?
- A checkbox on the subnet marked Public
- Its route table sends 0.0.0.0/0 to an Internet Gateway
correct: 1
explain: Public vs private is a route-table property, not a subnet flag. A subnet is public because its route table points default traffic at the IGW.

Q: An instance in a private subnet needs to download a patch from the internet. The traffic goes…
- directly out through the Internet Gateway
- up through a NAT Gateway in a public subnet, then out the Internet Gateway
correct: 1
explain: Private subnets have no IGW route. Outbound traffic is routed to a NAT Gateway (in a public subnet), which forwards it out through the IGW. Inbound from the internet remains blocked.

Q: A security group is best described as…
- a stateful, allow-only virtual firewall attached to an instance's network interface
- a stateless deny-list applied at the subnet boundary
correct: 0
explain: Security groups are stateful (return traffic is auto-allowed), allow-only (no deny rules), and attached per-instance. The stateless subnet-level filter is the network ACL.

Q: Why should a VPC's CIDR block be chosen to not overlap with the office network?
- Overlap causes routing conflicts when connecting the two later (VPN/Direct Connect)
- Overlap reduces the number of available addresses
correct: 0
explain: If the VPC and an on-prem network share addresses, packets can't be routed correctly between them. Non-overlapping ranges are a prerequisite for hybrid connectivity.

Q: AWS reserves how many IPs in every subnet?
- 2 (network and broadcast)
- 5 (network, VPC router, DNS, future use, broadcast)
correct: 1
explain: AWS reserves the first four and the last IP of each subnet. A /28 (16 addresses) therefore yields only 11 usable — sizing needs to account for this.
```
