Table of Contents#
- From One VPC to a Cloud Network Estate
- VPC/VNet Fundamentals — CIDR Planning and Why It's Hard to Change Later
- Subnets — Public, Private, and the Route Table That Actually Defines the Difference
- Route Tables — the Real Source of Truth for Where Traffic Goes
- Internet Gateways and NAT Gateways — Getting In and Out
- Security Groups vs. NACLs — Stateful and Stateless Defense in Depth
- VPC Peering — Direct, Point-to-Point Connectivity
- Transit Gateway — Solving Peering's Mesh Problem
- VPC Endpoints and PrivateLink — Reaching Cloud Services Without the Public Internet
- Bastion Hosts and Session Manager — Accessing Private Subnets Without Exposing SSH
- Site-to-Site VPN — IPsec and WireGuard for Hybrid Connectivity
- Dedicated Connections — Direct Connect, ExpressRoute, and Cloud Interconnect
- IP Address Management at Scale — Avoiding CIDR Collisions
- DNS Inside the VPC — Private Hosted Zones and Split-Horizon Resolution
- VPC Flow Logs — Network-Layer Observability
- Multi-Account Landing Zones — Shared VPC and Centralized Egress
- Multi-Region and Multi-Cloud Network Design Patterns
- Centralized Egress Filtering — Network Firewall as a Managed Inspection Point
- Full Worked Scenario: Redesigning the Platform's Network for a Second Region and a Compliance Boundary
- Common Mistakes and Interview Traps
- Worked Practice Problems
- Summary and What's Next
From One VPC to a Cloud Network Estate#
Parts 1 and 2 covered how traffic reaches your network and how it's spread across backends once it
arrives — this chapter is about the network it arrives into: how a cloud Virtual Private Cloud (VPC, or
VNet on Azure) is actually structured, and how many of them get wired together as an organization grows
past "one team, one VPC, one region." checkout-service, catalog-service, and inventory-service — the
same throughline platform from the first two chapters — started, like most platforms do, as a single VPC in
a single region, with a handful of engineers who could hold the entire network layout in their heads. This
chapter follows that platform's network as it grows past the point where that's still true: a second region for latency and
compliance, a separate VPC for a data-platform team that needs isolation, a VPN back to an on-premises
payment-processing system that can't move to the cloud, and the connectivity glue needed to make all of
that work without either a security nightmare or an unmanageable mesh of point-to-point links.
Every mechanism in this chapter answers some version of the same underlying question: as the number of VPCs, accounts, and regions grows, does connectivity and security policy stay something a small platform team can reason about directly, or does it silently become an unmanageable, ad hoc sprawl that nobody fully understands anymore? The patterns favored throughout this chapter — centralized IPAM, hub-and-spoke connectivity, centralized egress — all trade a small amount of upfront design discipline for keeping that question answerable at any scale the platform actually grows to.
Note
This chapter uses AWS terminology as the primary reference (VPC, Internet Gateway, Security Group) because it's the vocabulary most engineers encounter first, with GCP and Azure's equivalent concepts and naming called out inline — the underlying architecture patterns (subnetting, route tables, hub-and-spoke interconnect) are the same discipline across all three clouds, only the product names differ.
VPC/VNet Fundamentals — CIDR Planning and Why It's Hard to Change Later#
A VPC is a logically isolated, private IP address space inside a cloud provider's network — the cloud
equivalent of the private network namespaces covered from the kernel side in this site's Linux & Networking
Fundamentals series, just operated at the scale of an entire account/subscription rather than one host.
Creating one starts with choosing a CIDR block (commonly out of the RFC 1918 private ranges —
10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16) — and this single choice is disproportionately consequential,
because a VPC's primary CIDR block cannot be freely resized after subnets and resources already depend on
it, and an overlapping CIDR between two VPCs makes them fundamentally unable to peer or route to each
other later without a costly re-IP.
| Planning mistake | Why it bites later |
|---|---|
Choosing a CIDR too small (e.g. 10.0.1.0/24, 256 addresses) for "just one small team's VPC" | Every subnet, every load balancer ENI, every NAT gateway consumes addresses — teams routinely run out faster than expected once auto-scaling and multi-AZ redundancy are added |
| Choosing overlapping CIDRs across independently-created VPCs | Peering (this chapter, below) and Transit Gateway routing both require non-overlapping address space between anything that needs to talk to each other |
| Not reserving CIDR ranges per region/team upfront | Ad hoc CIDR assignment, done VPC-by-VPC as each team spins one up, is the single most common root cause of the "these two VPCs can never be connected without a full re-IP" problem discovered years later during a merger or platform consolidation |
Tip
Best practice: allocate CIDR ranges from a centrally planned, documented address space before any team
creates their first VPC — not after. A common, durable pattern: reserve a /8 (or as much of one as the
organization's cloud footprint realistically needs) and carve out non-overlapping /16s per region and
per major business unit, each further subdivided per VPC. This upfront discipline is what makes every
later chapter of this section — peering, Transit Gateway, multi-region — a straightforward configuration
exercise rather than a re-architecture project.
Subnets — Public, Private, and the Route Table That Actually Defines the Difference#
A VPC's CIDR block is subdivided into subnets, each pinned to exactly one Availability Zone — a subnet
itself is never multi-AZ, which is precisely why every genuinely highly-available architecture needs at
least one subnet per AZ per tier (public, private, data), not one subnet shared across zones. "Public" and
"private" are not a special subnet type or a checkbox — they are purely a description of what that
subnet's route table happens to point a 0.0.0.0/0 (default) route at, covered in depth in the next
section. A subnet with a default route to an Internet Gateway is, by that fact alone, a "public" subnet;
one without such a route (or with its default route pointed at a NAT gateway instead) is "private" — the
label is just descriptive shorthand for the routing configuration underneath it.
A minimally viable, genuinely-available two-tier layout — one public and one private subnet per AZ, each AZ independent so a single zone failure never takes down the whole tier.
⚙️ checkout-service's pods run in the private subnets — never directly internet-addressable — reached only
through the load balancer sitting in the public subnets, exactly the pattern covered from the
load-balancing side in Part 2. The database tier (RDS, in this platform's case) typically gets its own,
even-more-restricted subnet group with no route to the internet in either direction, not even via NAT — a
third tier beyond the simple two-tier diagram above, common in any architecture handling sensitive data.
Route Tables — the Real Source of Truth for Where Traffic Goes#
Every subnet is associated with exactly one route table, and that table — not any label, tag, or naming
convention — is the actual, enforced source of truth for where traffic from that subnet goes. A route
table is a simple, ordered list of destination CIDR blocks and the target (a gateway, a peering connection,
a NAT gateway, a Transit Gateway attachment) each one is routed through — conceptually identical to the ip route table on a single Linux host covered in this site's Linux & Networking Fundamentals series, just
scoped to an entire subnet's worth of resources rather than one machine.
# A representative private-subnet route table
Destination Target
10.0.0.0/16 local # intra-VPC traffic, always implicit
10.1.0.0/16 pcx-0123456789abcdef0 # VPC peering connection to another VPC
0.0.0.0/0 nat-0fedcba9876543210 # everything else goes out via NAT GatewayThe most-specific matching route always wins — the same longest-prefix-match principle covered in Part
1 for BGP applies identically here, just evaluated locally within one route table rather than propagated
across ASes. This is what lets a route table have both a broad 0.0.0.0/0 default (catch difficult
everything-else traffic) and a narrower, more specific route (like the peering-connection route above)
that takes priority for its own destination range without needing any explicit ordering or priority field.
Warning
A route table with no explicit route to a destination silently drops traffic to it — there is no error, no rejection message, just a connection that never establishes. This is a recurring, hard-to-diagnose failure signature specifically because it looks identical, from the calling side, to a security-group block: both produce "connection times out, nothing on the wire." The differentiator is where to look first — a security group block usually shows up in VPC Flow Logs as an explicit REJECT; a missing route never generates traffic on the wire at all for the destination network to see, because the packet never leaves the source subnet's own routing decision.
Internet Gateways and NAT Gateways — Getting In and Out#
Two distinct gateway types handle the two distinct directions of internet traffic, and conflating them is a common source of confusion for engineers new to cloud networking:
| Internet Gateway (IGW) | NAT Gateway | |
|---|---|---|
| Direction | Bidirectional — allows inbound and outbound | Outbound-only — a resource behind it can reach the internet, but the internet can never initiate a connection to it |
| Requires a public IP on the resource | Yes — a resource needs a public/Elastic IP to be reachable via an IGW | No — the NAT Gateway itself holds the public IP; resources behind it keep private IPs only |
| Where it's used | Public subnets — load balancers, bastion hosts | Private subnets — application servers that need outbound internet (fetching a package, calling a third-party API) but must never accept unsolicited inbound connections |
| Cost model | No hourly charge (AWS) | Hourly charge plus per-GB data processing charge — often a genuinely significant line item at real production data volumes |
A NAT Gateway is a real, if unglamorous, single point of failure and cost center worth deliberate design attention. It's zonal (an AWS NAT Gateway lives in one specific AZ), so a genuinely resilient multi-AZ architecture needs one NAT Gateway per AZ — routing every private subnet's outbound traffic through a NAT Gateway in a different AZ than the subnet itself both adds unnecessary cross-AZ data transfer charges and creates an availability dependency between zones that a well-designed architecture specifically avoids.
⚠️ From the trenches: inventory-service's platform team ran a single NAT Gateway (in one AZ) shared
across all three AZs' private subnets, reasoning that NAT Gateway "basically never goes down" and the
savings from not running three were worth it. During an AZ-level networking event (not a full AZ outage —
just degraded connectivity within that one zone), every private subnet across all three AZs lost outbound
internet access simultaneously, because they all depended on the single NAT Gateway sitting in the affected
zone. Services that needed outbound calls to third-party APIs (payment processing, fraud-detection checks)
failed across the entire platform, not just the one genuinely-affected AZ — turning a partial,
single-zone degradation into a full platform outage purely due to the NAT topology. The fix — one NAT
Gateway per AZ, with each AZ's private subnets routed only to their own zone's NAT Gateway — cost roughly
3x more per month in NAT Gateway hourly charges, a tradeoff the team now considers obviously correct in
hindsight, but hadn't consciously evaluated against the actual availability cost of the cheaper design at
the time it was built.
NAT Gateway Alternatives, and the IPv6 Case Where NAT Isn't Needed at All#
A managed NAT Gateway isn't the only option, and knowing the alternatives — and when NAT is unnecessary entirely — rounds out the outbound-connectivity picture:
- NAT instances (a plain EC2 instance running NAT software, the pre-managed-NAT-Gateway approach) are now largely legacy — cheaper at very small scale, but self-managed (patching, scaling, HA all become the team's own problem again) in a way that rarely beats the managed NAT Gateway's operational simplicity once the true cost of that ongoing maintenance burden is counted honestly.
- Egress-only Internet Gateways are the IPv6-specific equivalent of a NAT Gateway's purpose (outbound-only internet access for a private subnet) without actually doing address translation at all — a direct consequence of Part 1's IPv6 coverage: since every IPv6 address is globally routable by design, there's no private-address-to-public-address mapping to perform in the first place. An egress-only Internet Gateway simply enforces the directionality restriction (outbound-initiated only, exactly like a NAT Gateway's behavior) without the translation NAT's own name implies, and — notably — without NAT's associated hourly or per-GB processing charge, since there's no translation work being done.
Note
This is precisely the mechanism-level detail behind Part 1's observation that IPv6 doesn't create NAT's usual address-conservation pressure: a dual-stack subnet's IPv4 traffic still needs a real NAT Gateway, while its IPv6 traffic can use the cheaper, translation-free egress-only Internet Gateway for the identical "outbound yes, inbound no" policy — a genuine, measurable cost difference between the two address families for outbound-heavy workloads once IPv6 adoption is far enough along to matter.
Security Groups vs. NACLs — Stateful and Stateless Defense in Depth#
Two independent, layered filtering mechanisms guard traffic in and out of a VPC, and — echoed from Part 1's netfilter-adjacent material, but at the cloud-managed layer instead of the kernel — the distinction between stateful and stateless filtering is the single most important thing to internalize correctly:
| Security Group | Network ACL (NACL) | |
|---|---|---|
| Scope | Attached to individual resources (an EC2 instance, an ENI, an RDS instance) | Attached to a subnet — applies to everything in it |
| Stateful? | Yes — allow inbound on port 443, and the return traffic is automatically permitted, no matching outbound rule needed | No — must explicitly permit both the inbound request and the outbound return traffic as separate rules |
| Rule evaluation | All rules evaluated; if any rule allows it, it's allowed (no explicit deny needed, only allow rules exist) | Rules are numbered and evaluated in order; first match wins, and explicit DENY rules are a real, first-class feature |
| Default behavior | Deny all inbound, allow all outbound (typical default) | Allow all in both directions (default NACL) — a custom NACL you create starts fully closed |
The stateful/stateless distinction has a concrete, easy-to-get-wrong consequence: a Security Group rule
allowing inbound HTTPS (443) needs no matching outbound rule for the response to leave — the state table
handles it automatically. A NACL doing the exact same job needs an inbound rule allowing 443 and a
separate outbound rule allowing the ephemeral port range (typically 1024-65535) the client's OS
assigned for that connection's return traffic — a NACL that only opens 443 inbound, with no ephemeral-range
outbound rule, silently breaks every connection through it despite looking, at a glance, like it should
work.
The Security Group's return path (solid) needs nothing extra configured; the NACL's return path (dashed) needs its own explicit outbound rule — a genuinely common source of "works from one direction, silently fails from the other" tickets.
Tip
Best practice, echoing the equivalent Part 1/2 guidance for other layers: use both, deliberately, as genuine defense in depth — not as redundant duplicate controls. Security Groups doing fine-grained, resource-level allow rules; NACLs doing coarse, subnet-wide guardrails (an explicit DENY on a known-bad IP range, or a blanket rule blocking a decommissioned legacy port range across an entire subnet regardless of what any individual resource's Security Group says) — a NACL DENY is the one mechanism in this pair that can override an overly permissive Security Group, which is exactly the scenario it exists to guard against.
VPC Peering — Direct, Point-to-Point Connectivity#
VPC peering establishes a direct, private network connection between exactly two VPCs, letting resources in either communicate using private IP addresses as if they were on the same network — no internet gateway, no NAT, no public exposure required. Peering is non-transitive: if VPC A peers with VPC B, and VPC B peers with VPC C, traffic from A cannot reach C through B — each pair that needs to communicate needs its own direct peering connection, a constraint that becomes the central limitation covered in the next section.
# Requesting a peering connection (the accepting side must separately accept it)
aws ec2 create-vpc-peering-connection \
--vpc-id vpc-checkout-us-east-1 \
--peer-vpc-id vpc-shared-data-us-east-1
# Both sides' route tables need an explicit route pointing at the peering
# connection for the other VPC's CIDR — peering alone does not add routesPeering's core scaling problem is combinatorial: connecting N VPCs in a full mesh (every VPC able to
reach every other one directly) requires N(N-1)/2 peering connections — the exact same growth curve
covered for iBGP full-mesh in Part 1, and it becomes unmanageable at roughly the same scale (a few dozen
VPCs). Ten VPCs need 45 connections; twenty need 190. Each connection also needs its own explicit route-table
entries on both sides, multiplying the operational overhead further. This is precisely the problem the next
section's Transit Gateway pattern exists to solve.
Transit Gateway — Solving Peering's Mesh Problem#
A Transit Gateway (AWS's name; GCP's Network Connectivity Center and Azure's Virtual WAN are the
equivalent concepts) is a managed, regional hub that every VPC attaches to once, with the gateway itself
handling routing between every attached VPC — replacing peering's combinatorial mesh with a hub-and-spoke
model where N VPCs need only N attachments, not N(N-1)/2 connections.
Five attachments instead of ten peering connections for the same full connectivity — and adding a sixth VPC later means one new attachment, not five new peering connections.
The tradeoff, covered from the routing-mechanics side in Part 1's peering-vs-transit discussion, applies here too: Transit Gateway adds a real extra hop (measurably higher latency than direct peering, though usually immaterial for typical application traffic) and a per-attachment, per-GB cost that direct peering doesn't carry. Transit Gateway's real, differentiating power is multiple route tables — a single Transit Gateway can maintain several independent routing domains, so (for example) a compliance-sensitive VPC can be attached with routes that only reach a specific subset of the other attached VPCs, while the rest of the network sees a completely different routing view — enforcing network-level segmentation centrally, at the hub, rather than needing to replicate the same segmentation logic across every individual VPC's own route tables and security groups.
Tip
Best practice: default new inter-VPC connectivity needs to Transit Gateway once an organization has more
than roughly 4-5 VPCs that need to talk to each other, even if peering would technically still work at
that count. The migration cost of moving from an established peering mesh to Transit Gateway later (new
attachments, route table changes, a cutover window) is real and avoidable by choosing the hub-and-spoke
model before the mesh gets large enough to hurt — waiting until the N(N-1)/2 math is already painful is
the expensive way to learn this lesson.
VPC Endpoints and PrivateLink — Reaching Cloud Services Without the Public Internet#
A subtlety easy to miss: calling a cloud provider's own managed service (S3, DynamoDB, a managed Kafka service) from inside a private subnet with no internet route normally fails, because those services' API endpoints are, by default, public internet addresses — even though the traffic never leaves the cloud provider's own network physically, it's still routed, from the VPC's perspective, as internet-bound traffic requiring a route to an Internet Gateway or NAT Gateway to reach at all.
VPC Endpoints solve this by placing a private, in-VPC entry point for a specific AWS service directly inside the VPC's own route table — traffic to that service never needs a route to the internet at all, improving both security posture (no NAT Gateway data-processing charges for that traffic, no possibility of that traffic being intercepted or exfiltrated via a compromised NAT path) and, frequently, latency.
- Gateway endpoints (S3, DynamoDB specifically): free, implemented as a route-table target — a genuinely easy, low-cost win with essentially no downside for any private subnet that talks to either service.
- Interface endpoints (most other services — Secrets Manager, KMS, most managed-service APIs): an ENI with a private IP placed directly in your subnet, billed hourly plus per-GB, functioning as a private DNS target for the service.
PrivateLink extends the same underlying mechanism to expose a custom service — one your own organization runs, in your own VPC — privately to other VPCs (including ones belonging to a different AWS account entirely, a common pattern for a platform team offering a shared internal service to multiple product teams' own VPCs) without peering or Transit Gateway at all, and critically, without exposing that service's IP space or requiring any inbound route from the consumer's side — a one-way, tightly-scoped connectivity primitive specifically suited to a shared internal platform API that many otherwise-unrelated VPCs need to reach.
Bastion Hosts and Session Manager — Accessing Private Subnets Without Exposing SSH#
Engineers still need occasional interactive access to resources sitting in a private subnet — debugging a production issue that genuinely requires shelling into a box, running a one-off database migration script from inside the network boundary. The traditional pattern, and the more modern replacement for it, are worth contrasting directly, because the traditional one carries a real, persistent security liability that's easy to under-appreciate until it's exploited.
- The bastion host (jump box) pattern: a single, hardened EC2 instance sits in a public subnet with SSH (port 22) open to the internet (or, at best, to a specific allowlisted IP range), and engineers SSH into the bastion first, then SSH again from the bastion into the actual private-subnet target. This works, and is still common in older architectures, but it means a real, internet-facing SSH port is permanently exposed — a continuous attack surface (credential-stuffing attempts, unpatched-CVE exploitation risk) that exists every hour of every day, whether or not anyone is actually using it in a given moment.
- AWS Systems Manager Session Manager (and the equivalent identity-aware-proxy-based tooling on GCP and Azure) replaces the bastion entirely: an agent running on the target instance itself establishes an outbound connection to the Systems Manager service (no inbound port needs to be open at all — not even 22), and an authorized engineer's session is brokered through that outbound channel, authenticated via the cloud provider's own IAM rather than a separately-managed SSH key. The target instance needs zero inbound rules from the internet, and often zero inbound rules at all — the entire access path is IAM-authenticated and fully audit-logged (every session, every command, centrally) by default, a real security and compliance upgrade over SSH key management sprawl.
The bastion pattern's inbound-exposed port is a permanent attack surface even when idle; Session Manager's outbound-only connection has no equivalent inbound exposure at all — the private instance never listens on a network port for interactive access.
Tip
Best practice: default to an identity-aware, outbound-only access broker (Session Manager or its
equivalent) for all new architectures, and treat any remaining bastion host as legacy debt to actively
retire, not a pattern to keep deploying. Where a genuine, narrow exception exists (a legacy tool that
specifically requires raw SSH and can't be adapted), scope the bastion's inbound rule as tightly as
possible — a specific corporate VPN egress IP, never 0.0.0.0/0 — and treat it as a deliberately accepted,
actively monitored risk rather than a default architectural choice.
Site-to-Site VPN — IPsec and WireGuard for Hybrid Connectivity#
Not every workload lives in the cloud — checkout-service's platform still depends on an on-premises
payment-settlement system that, for regulatory and legacy-integration reasons, isn't migrating. A
site-to-site VPN creates an encrypted tunnel between the cloud VPC and the on-premises network over the
public internet, avoiding the cost and lead time of a dedicated physical connection (covered next) at the
expense of depending on best-effort public internet transit for the underlying path.
Two protocol families dominate production site-to-site VPN deployments today, and the choice genuinely matters:
| IPsec | WireGuard | |
|---|---|---|
| Maturity | Decades old, an IETF standard, universally supported by enterprise routers and every major cloud's managed VPN service | Newer (first stable in 2020), smaller and more auditable codebase (~4,000 lines vs. IPsec implementations' hundreds of thousands) |
| Configuration complexity | Genuinely complex — multiple negotiation phases, many interoperability-affecting parameter choices | Deliberately minimal — a small, opinionated set of modern cryptographic primitives, little room for misconfiguration |
| Performance | Solid, though implementation-dependent | Measurably faster in most published 2026 benchmarks, and noticeably better on unstable/high-latency links |
| Where it dominates | Enterprise site-to-site VPNs between routers, regulated deployments where IPsec's IETF Standards Track status specifically matters, native mobile-carrier and iOS integration | Newer deployments where both endpoints support it and raw throughput/simplicity is prioritized over legacy interoperability |
Every major cloud's managed site-to-site VPN product (AWS Site-to-Site VPN, Azure VPN Gateway, Google Cloud VPN) is built on IPsec specifically because of its universal enterprise-router interoperability — a practical constraint that keeps IPsec the default choice for this specific use case even as WireGuard gains ground elsewhere (a service mesh's own inter-cluster tunneling, or a modern zero-trust network-access product, being two areas where WireGuard has seen faster adoption than in the classic router-to-router site-to-site case).
Note
A single cloud-managed VPN connection is, like a single NAT Gateway, a real availability risk if not deliberately made redundant — production site-to-site VPN deployments run two tunnels (most managed VPN products provision this automatically, terminating on two separate physical devices at the cloud provider's edge) specifically so a single tunnel's failure doesn't sever hybrid connectivity entirely.
Dedicated Connections — Direct Connect, ExpressRoute, and Cloud Interconnect#
For an organization with sustained, high-volume, latency-sensitive traffic between on-premises infrastructure and the cloud — enough to justify the cost and lead time — a dedicated connection (AWS Direct Connect, Azure ExpressRoute, Google Cloud Interconnect) provides a private, physical network link directly into the cloud provider's network, bypassing the public internet entirely. This isn't a VPN over a dedicated line — it's a genuinely different physical/logical connection, typically established at a carrier-neutral colocation facility (frequently the same kind of facility that hosts the IXPs covered in Part 1), with the customer either bringing their own cross-connect or working through a Direct Connect/ExpressRoute Partner who already has presence there.
| Site-to-Site VPN | Dedicated Connection (Direct Connect/ExpressRoute) | |
|---|---|---|
| Underlying transport | Public internet (encrypted) | Private physical circuit — never touches the public internet |
| Setup time | Minutes to hours | Weeks to months (physical circuit provisioning) |
| Bandwidth consistency | Best-effort, shared with all other internet traffic on the path | Dedicated, guaranteed bandwidth |
| Cost model | Low, usage-based | Higher fixed cost, but often cheaper per-GB at real sustained high volume |
| Typical use | Quick to establish, moderate/variable volume, or as a backup path | Sustained high-volume, latency-sensitive, or compliance-driven requirement to avoid public internet transit entirely |
Production hybrid architectures at real scale frequently run both, with the dedicated connection as primary and a site-to-site VPN as an automatic failover path — the dedicated connection's own physical circuit is a real single point of failure risk (a backhoe cutting the wrong fiber run is the industry's long-running dark joke about this exact failure mode) unless paired with either a second, physically diverse dedicated connection or a VPN fallback that can absorb traffic during an outage.
IP Address Management at Scale — Avoiding CIDR Collisions#
Returning to the CIDR-planning discipline from earlier in this chapter, now at the scale that makes it matter most: once an organization has dozens of VPCs across multiple accounts, regions, and teams, ad hoc CIDR assignment reliably produces overlapping address ranges that block exactly the connectivity patterns this chapter has covered — two VPCs with overlapping CIDRs cannot peer, cannot share a Transit Gateway route table cleanly, and cannot be bridged by a VPN without additional NAT complexity layered on top specifically to work around the collision.
- AWS IPAM (and the equivalent GCP/Azure IP-management tooling) is the managed-service answer: a central pool of address space, carved into nested, non-overlapping allocations per region/account/team, with automatic collision detection at allocation time rather than discovery months later during an attempted peering request.
- The alternative to a managed IPAM tool is the same discipline covered earlier applied as an actual, enforced process: a documented central CIDR registry (even a well-maintained spreadsheet or a Terraform-managed data source, for a smaller organization), with every new VPC's CIDR request checked against it before creation — the specific mechanism matters less than the discipline of never allocating a new VPC's address space without checking it against everything that already exists.
Important
RFC 1918 private space (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16) is finite, and a large
multi-account cloud estate can genuinely run into real capacity pressure within it — particularly the
smaller 192.168.0.0/16 and 172.16.0.0/12 ranges. Planning CIDR allocation with the organization's
realistic multi-year growth in mind, not just its current VPC count, avoids a much more painful
renumbering exercise once the initially-generous-looking address plan turns out not to be.
⚙️ Worked example: checkout-service's platform team reserves 10.0.0.0/8 as the organization's entire
private address space at the start of their cloud adoption, carving it into /16s: 10.0.0.0/16 for
us-east-1 production, 10.1.0.0/16 for eu-central-1 production (used in this chapter's own worked
scenario below), 10.2.0.0/16 for the shared-services/network account, and 10.100.0.0/16 reserved,
deliberately unused, as headroom for a future third region — planned before it's needed, exactly the
discipline this section argues for. Every individual VPC created afterward carves its own smaller CIDR
(typically a /20 or /21, sized for real expected instance/ENI count including headroom for auto-scaling)
out of its region's reserved /16, checked against the central registry before creation — a five-minute
process that has, in this platform's three years of operation since, avoided a single CIDR collision despite
the platform growing from one VPC to over a dozen across three accounts.
DNS Inside the VPC — Private Hosted Zones and Split-Horizon Resolution#
A cloud DNS service (Route 53, Cloud DNS, Azure DNS) commonly serves two genuinely distinct roles that are
easy to conflate: public DNS (resolving checkout-service's public API hostname for the entire
internet, tied directly to the anycast/BGP mechanics from Part 1) and private DNS (resolving internal
service names — checkout-service.internal, an RDS endpoint's hostname — only for resolvers inside the VPC,
never exposed publicly at all).
A private hosted zone attached to one or more VPCs answers queries for internal-only names, and is a significant operational upgrade over hardcoding private IP addresses directly into application configuration — a backend instance can be replaced, its private IP can change, and every dependent service continues working unmodified because they reference the stable DNS name, not the IP that happens to sit behind it today.
Split-horizon DNS (the same zone name resolving differently depending on whether the query originates
inside or outside the VPC) is the pattern that lets checkout-service.example.com resolve to a public,
internet-facing IP for external customers while an internal checkout-service.example.com query (from
inside the VPC, hitting a private hosted zone with a higher-priority match) resolves to a private, internal
load balancer IP for service-to-service traffic that should never leave the VPC or pass through a
public-facing load balancer at all — reducing both latency (no unnecessary round trip out to the internet
and back) and unnecessary public attack surface for traffic that was always meant to stay internal.
VPC Flow Logs — Network-Layer Observability#
Every mechanism covered so far in this chapter — route tables, Security Groups, NACLs, peering — makes a silent, binary allow/deny decision on every packet, with no default record of what it decided or why. VPC Flow Logs capture metadata about IP traffic flowing through a VPC's network interfaces — source and destination IP/port, protocol, byte/packet counts, and critically, whether the flow was ACCEPTed or REJECTed — without capturing the packet payload itself (a deliberate design choice, keeping flow logs usable for network troubleshooting without also becoming a full packet-capture compliance/privacy liability).
version account-id interface-id srcaddr dstaddr srcport dstport protocol packets bytes start end action log-status
2 123456789012 eni-0abc 10.0.11.5 198.51.100.20 443 52341 6 10 1200 1719500000 1719500060 ACCEPT OK
2 123456789012 eni-0abc 10.0.11.5 10.0.99.10 5432 34521 6 0 0 1719500061 1719500121 REJECT OK
The second row's REJECT — a connection attempt to port 5432 (Postgres) that never completed — is exactly
the kind of signal that distinguishes a Security Group block (which appears here, explicitly) from a
missing route table entry (which does not — a route table drop happens before flow logging ever sees the
packet at all, since there's no interface to attach the log to).
Tip
Best practice: enable VPC Flow Logs by default on every VPC before it's needed for an actual incident,
not reactively after one. The single highest-value diagnostic use: when a connection times out and it's
unclear whether a Security Group, a NACL, or a missing route is responsible, a flow log entry showing
REJECT definitively confirms a Security Group or NACL block occurred (narrowing the search
immediately), while the complete absence of any flow log entry for that connection attempt at all
points at a routing problem instead — a fast, mechanical way to distinguish the two failure classes
covered separately earlier in this chapter, rather than guessing.
Multi-Account Landing Zones — Shared VPC and Centralized Egress#
A large organization rarely runs every workload inside one flat account with one VPC — the now-standard practice (AWS's Control Tower/Landing Zone, GCP's equivalent Shared VPC and Resource Manager hierarchy, Azure's Management Groups) is one AWS account (or GCP project) per team or workload, with networking centralized and shared from a dedicated network account — isolating a security incident or a cost overrun in one team's account from ever directly touching another's, while still providing centrally managed, consistent connectivity.
Shared VPC / Transit Gateway sharing is the mechanism that makes this practical: a central network account owns the actual VPC (or the Transit Gateway), and shares specific subnets or attachments with other accounts via the cloud provider's cross-account resource-sharing primitive (AWS Resource Access Manager, GCP's Shared VPC host/service project model) — application teams deploy their workloads into subnets they don't own or directly control the routing for, while the central network team retains sole ownership of the actual network architecture, route tables, and Transit Gateway attachments.
Application teams get self-service deployment into a subnet without needing (or being able to accidentally misconfigure) the shared network's own routing, NAT, or Transit Gateway architecture.
Centralized egress is the specific, high-value pattern this model enables: instead of every team's account running and paying for its own NAT Gateway fleet, all outbound internet traffic from every shared account routes through a small number of NAT Gateways owned centrally in the network account — cutting the NAT Gateway cost multiplication that would otherwise scale linearly with team/account count, and giving the security team a single, centrally-controlled, centrally-logged egress point to apply organization-wide egress filtering (an explicit allowlist of permitted outbound destinations, a common requirement in regulated environments) rather than needing to replicate and audit that policy separately in every individual team's account.
Warning
Centralized egress through Transit Gateway is a genuine, real cost consideration, not just an architectural nicety — Transit Gateway charges per-GB for data crossing it, on top of whatever the NAT Gateway itself charges, meaning centralizing egress can, for a high-outbound-volume workload, cost more in aggregate data-processing fees than a per-account NAT Gateway would have, even though it centralizes and reduces the fixed hourly NAT Gateway cost. Modeling the actual expected data volume before committing to a centralized-egress architecture avoids a genuinely surprising line item on the first full month's cloud bill after the migration.
Multi-Region and Multi-Cloud Network Design Patterns#
Bringing this chapter's pieces together at the scale most large platforms eventually operate at:
- Multi-region, single cloud: each region gets its own VPC (never a single VPC spanning regions — cloud VPCs are fundamentally regional constructs), connected via the cloud provider's own inter-region backbone (AWS's inter-region VPC peering, or a Transit Gateway peering connection between regions) rather than routing inter-region traffic out over the public internet.
- Multi-cloud: no single managed hub-and-spoke product spans providers — connectivity between an AWS VPC and a GCP VPC genuinely requires either a site-to-site VPN between the two clouds' own gateway products, or a third-party network-as-a-service overlay (Aviatrix and similar products exist specifically to provide the missing "Transit Gateway that spans clouds" abstraction). This is a real, material added complexity most single-cloud platforms never have to plan for, and a driver of genuine platform-team headcount at organizations that adopt multi-cloud primarily for negotiating leverage rather than a workload-specific technical need.
- Compliance-driven regional isolation:
checkout-service's EU expansion (first introduced in Part 1's anycast worked scenario) needs its EU customer data to stay within the EU region's network boundary as a hard requirement, not just a preference — implemented at the network layer by deliberately not Transit-Gateway-connecting the EU VPC to the US VPC for data-plane traffic, even though both regions are part of the same overall platform, with only a narrowly-scoped, audited connection (a specific PrivateLink endpoint, not a general routable peering) permitted for the specific cross-region metadata sync the compliance review actually approved.
Centralized Egress Filtering — Network Firewall as a Managed Inspection Point#
The centralized-egress pattern from earlier in this chapter (all outbound traffic routed through a small number of NAT Gateways in a shared network account) creates a natural, single choke point for something beyond plain address translation: deep, policy-driven inspection of what's actually leaving the network, not just permitting or denying based on IP/port the way a Security Group or NACL does.
A managed network firewall (AWS Network Firewall, Azure Firewall, GCP Cloud NGFW) sits inline at exactly that centralized-egress choke point and applies stateful, often domain-name-aware and even payload-aware rules — "outbound HTTPS is only permitted to an explicit allowlist of approved third-party API domains," "block outbound traffic matching known malware command-and-control signatures," "alert on any outbound connection attempting to reach a raw IP address with no matching DNS-based allowlist entry at all" (a real, common signature of a compromised host attempting to exfiltrate data or reach a C2 server directly, bypassing normal DNS-resolved traffic patterns entirely).
A Security Group or NACL alone has no concept of "which domain is this HTTPS connection actually going to" — that's payload/SNI-level inspection, a genuinely different, deeper capability layered on top of the IP/port-level controls covered earlier in this chapter.
Important
A managed network firewall is a real, additional operational and cost commitment, not a checkbox — it's the right tool specifically when egress needs to be policy-restricted (a regulated environment, a genuine data-exfiltration threat model), not a default every architecture needs. For a platform without that specific requirement, the Security Group/NACL/route-table layers covered earlier in this chapter are usually sufficient, and adding inline deep packet inspection to every outbound connection is real added latency and cost with no corresponding benefit if there's no actual policy it's meant to enforce beyond what a plain allow/deny at the IP/port level already provides.
Full Worked Scenario: Redesigning the Platform's Network for a Second Region and a Compliance Boundary#
Following directly from Part 1's checkout-service EU-expansion scenario, this chapter's worked example
covers the network architecture underneath that expansion in full, rather than just the anycast frontend
covered there.
Starting state: a single VPC in us-east-1 (CIDR 10.0.0.0/16), with the standard three-tier subnet
layout from earlier in this chapter, one NAT Gateway per AZ, and a Transit Gateway already in place
connecting it to a separate shared-data VPC used by the analytics team.
The redesign, applying this chapter's concepts in sequence:
- CIDR planning first: the EU VPC is allocated
10.1.0.0/16— deliberately non-overlapping with the existingus-east-1VPC's10.0.0.0/16, following the central IPAM discipline covered earlier, so that any future connectivity requirement between the two remains possible without a re-IP. - Regional Transit Gateway peering, but deliberately scoped: the EU Transit Gateway is peered to the US Transit Gateway specifically for a narrow, audited set of prefixes (the internal admin tooling subnet, needed for platform-team operational access) — not a blanket "connect everything" peering, directly implementing the compliance-driven isolation pattern from the previous section.
- No production data-plane route exists between the two regions' application subnets at all — this is the actual enforcement mechanism for "EU customer data stays in the EU," verified concretely by inspecting the Transit Gateway's route table for the EU attachment and confirming the US application subnet's CIDR is genuinely absent from it, not merely restricted by a Security Group that a future change could accidentally loosen.
- VPC Endpoints for every AWS-managed service the EU workloads depend on (Secrets Manager, KMS), so that a genuinely private, EU-region-only path exists for every dependency — avoiding any scenario where a seemingly-internal API call to a managed AWS service transits back through a US-region endpoint by default.
What the compliance audit caught that the network diagram alone hadn't surfaced: the redesign correctly
blocked EU-to-US data-plane routing at the network layer, but the audit found that checkout-service's own
application-level configuration still pointed its centralized logging pipeline at a single, US-region log
aggregation endpoint — meaning EU request logs, which could contain customer PII depending on what got
logged, were being shipped out of the EU region entirely, over the legitimately-permitted admin/tooling
Transit Gateway path, silently bypassing the very isolation the network redesign was built to enforce. The
lesson that generalizes, and the reason this scenario is worth including in a networking chapter rather
than treating it as purely an application-layer concern: network-layer isolation only enforces what
traffic the network topology allows — it says nothing about what an application deliberately chooses to
send over a connection the network topology does, correctly, permit. The fix required both a
network-layer change (a regional logging endpoint, so EU logs never needed a cross-region path in the first
place) and an application-configuration change — a reminder that a genuinely audited compliance boundary
needs review at every layer, not just the one this chapter's own material covers.
Common Mistakes and Interview Traps#
| Mistake | Why it's wrong | What to say instead |
|---|---|---|
| "Security Groups and NACLs do the same job, so one is redundant" | Security Groups are stateful and resource-scoped; NACLs are stateless and subnet-scoped, and only NACLs support explicit DENY rules | They're complementary defense-in-depth layers, not duplicates — a NACL DENY can override an overly permissive Security Group |
| "VPC peering is transitive — if A peers with B and B peers with C, A can reach C" | Peering connections are strictly point-to-point and non-transitive by design | A needs its own direct peering (or a shared Transit Gateway) with C — B being in the middle doesn't create a path |
| "A private subnet has no internet access, period" | A private subnet with a NAT Gateway route has outbound-only internet access — it's "private" because nothing can initiate an inbound connection to it, not because it's fully air-gapped | Private means no inbound internet exposure; outbound is a separate, independently configured concern |
| "IPsec and WireGuard are interchangeable — just pick whichever" | IPsec remains the near-universal choice for classic enterprise site-to-site VPN specifically due to router interoperability; WireGuard's advantages matter more in different use cases | Choose based on the actual endpoints' support and the interoperability requirement, not just raw performance benchmarks |
| "A Transit Gateway is strictly an upgrade over peering in every case" | Transit Gateway adds real latency and per-GB cost that direct peering doesn't have | For a small, stable number of VPCs, direct peering can still be the simpler, cheaper, lower-latency choice — Transit Gateway earns its keep at real scale |
Worked Practice Problems#
Problem 1: An application in a private subnet can successfully connect to an internal service in a peered
VPC, but a curl to an internet-hosted public API from that same private subnet times out. The route table
has a route for the peered VPC's CIDR, and a NAT Gateway exists in the VPC. What's the most likely
misconfiguration?
Answer: The private subnet's route table is very likely missing (or has an incorrect) default route
(0.0.0.0/0) pointing at the NAT Gateway — the presence of a working, specific route to the peered VPC's
CIDR doesn't imply the separate, broader default route to the NAT Gateway is also correctly configured;
each destination needs its own explicit route table entry, and one working route says nothing about
another.
Problem 2: Two VPCs, 10.0.0.0/16 and 10.0.128.0/17, need to be connected. What's wrong with this
plan before any peering request is even attempted?
Answer: 10.0.128.0/17 falls entirely within 10.0.0.0/16's address range (10.0.0.0/16 spans
10.0.0.0 through 10.0.255.255, and 10.0.128.0/17 spans 10.0.128.0 through 10.0.255.255 — a
strict subset). These two VPCs have overlapping CIDR blocks and cannot be peered at all — the peering
request itself would be rejected. This is exactly the outcome the central IPAM/CIDR-planning discipline
covered in this chapter exists to prevent, and the only real fix at this point is re-IPing one of the two
VPCs entirely, a disruptive, high-effort remediation that upfront planning avoids.
Problem 3: A compliance requirement states that a specific subnet's traffic must never reach the public internet, under any circumstance, even if a future engineer misconfigures a route table. What's the most robust way to enforce this, beyond simply "don't add a NAT Gateway route"?
Answer: Don't rely on the absence of a route as the sole enforcement mechanism, since a future change
could add one. A more robust approach layers multiple independent controls: no NAT Gateway or Internet
Gateway route configured for that subnet (removes the easy path entirely), a restrictive NACL on the subnet
with an explicit DENY for any destination outside the organization's known private CIDR ranges (a control
that survives even if a route table change is accidentally made, since the NACL is evaluated independently
of the route table), and, where the platform supports it, an automated compliance-as-code check (in the
Terraform/IaC pipeline covered in this site's own Terraform series) that fails a deploy outright if it
would introduce a route to 0.0.0.0/0 on a subnet tagged as compliance-restricted — turning "don't
misconfigure this" from a matter of individual engineer discipline into an automatically enforced guardrail.
Problem 4: A security team wants to guarantee that no workload can exfiltrate data to an arbitrary external IP address, only to a specific, approved list of third-party API domains. A Security Group allowlisting outbound HTTPS to specific IP ranges is proposed as the fix. Why does this fall short of the actual requirement, and what's the right layer to enforce it at?
Answer: A Security Group's outbound rules operate on IP addresses/CIDR ranges, not domain names — and most third-party APIs (and every CDN-fronted one) resolve to IP addresses that change over time and are shared across many unrelated customers of that same CDN/cloud provider, making a stable, accurate IP-based allowlist impractical to maintain and, worse, prone to either blocking legitimate traffic when an IP rotates or under-restricting because the allowlisted range is far broader than the one specific domain actually intended. The requirement described — "only these specific domains" — needs domain-aware (SNI or DNS-based) inspection, which is exactly what a managed network firewall provides, sitting at the centralized egress point covered earlier in this chapter, rather than something a Security Group's IP/port-only model can express at all.
Summary and What's Next#
Every mechanism in this chapter — CIDR planning, subnets and route tables, the peering-vs-Transit-Gateway scaling curve, VPN and dedicated connections, and DNS split-horizon resolution — is the infrastructure that Parts 1 and 2's routing and load-balancing concepts actually run on top of inside a cloud account. Getting this layer right (or wrong) shapes every later networking decision a platform makes, and — as this chapter's worked scenario showed — even a network layer that's genuinely correctly designed only enforces what the network topology allows, not what an application built on top of it actually chooses to do.
The recurring theme worth carrying forward: almost every mechanism in this chapter exists in layered pairs, not single controls — Security Groups paired with NACLs, a NAT Gateway paired with an egress-only Internet Gateway for the address family that doesn't need translation, a dedicated connection paired with a VPN fallback, direct peering paired with a Transit Gateway once the mesh outgrows it. Treating any one of these as sufficient on its own, rather than as one layer of a deliberately redundant design, is the thread running through most of this chapter's From the Trenches callouts.
Part 4 moves from network topology to the security and protocol layer riding on top of it: the TLS
handshake in full mechanical detail, how mTLS extends it to mutual authentication between services, and how
HTTP itself has evolved from HTTP/1.1 through HTTP/2 to HTTP/3's QUIC-based transport — the protocols that
actually carry checkout-service's traffic across every network this series has covered so far, whether
that traffic is crossing the public internet from Part 1, being split across a backend fleet by Part 2's
load balancer, or routed between the VPCs and subnets this chapter just laid out.