Networking: VPC Deep Dive
A note on this part's depth: AWS networking is consistently one of the most heavily tested areas in real SRE, DevOps, and Platform Engineering interviews — and one of the most common sources of real production incidents. This part goes noticeably deeper than the others in this series, covering every major networking service, with heavy CLI usage and explicit best practices throughout, on purpose.
Table of Contents#
- Why Networking Deserves This Much Depth
- The VPC — Your Own Private Network Inside AWS
- CIDR Blocks — Planning IP Address Space Correctly
- Subnets — Carving Up the VPC
- Public vs Private vs Isolated Subnets
- Route Tables — How Traffic Actually Finds Its Way
- The Internet Gateway
- NAT Gateway vs NAT Instance
- A Full Worked Three-Tier VPC
- Security Groups — Stateful, Instance-Level Firewalls
- Network ACLs — Stateless, Subnet-Level Firewalls
- Security Groups vs NACLs — The Full Comparison
- Security Group Chaining — Referencing Other Groups
- VPC Peering
- Transit Gateway — Solving the Peering-Mesh Problem
- VPC Endpoints — Gateway Endpoints
- VPC Endpoints — Interface Endpoints and PrivateLink
- Site-to-Site VPN
- AWS Direct Connect
- Choosing Between VPN, Direct Connect, and the Public Internet
- VPC Flow Logs — Seeing What's Actually Happening
- AWS Network Firewall
- VPC Lattice — A Newer, Application-Layer Alternative
- PrivateLink vs VPC Peering vs Transit Gateway — Choosing the Right Connectivity
- DNS Resolution Inside a VPC
- IPv6 in AWS — A Brief, Practical Note
- IPAM — IP Address Manager
- VPC Sharing via AWS Resource Access Manager (RAM)
- VPC Reachability Analyzer — Automated Path Diagnosis
- AWS Global Accelerator — A Brief, Practical Note
- A Networking Incident Troubleshooting Playbook
- Networking Best Practices — The Consolidated Checklist
- Common Mistakes
- Worked Practice Problems
- Summary and What's Next
Why Networking Deserves This Much Depth#
Every service covered elsewhere in this series — EC2 (Part 3), databases (Part 6), containers (Part 7), load balancers (Part 8) — ultimately lives inside the network fabric this part describes. A misconfigured route table or an overly permissive security group doesn't just cause a bug; it's routinely the actual root cause behind real security incidents and real outages. This part is the AWS-specific, deeply concrete implementation of ideas already introduced generically across this course: the "network segmentation" and "bulkhead" patterns from the Reliability & Architecture Patterns series, the TCP/IP and DNS fundamentals from the Linux & Networking series, and the Zero Trust principles that Part 9 (Security) will build directly on top of what's covered here.
The VPC — Your Own Private Network Inside AWS#
A Virtual Private Cloud (VPC) is an isolated, private network you define within a single AWS Region — your own slice of AWS's network fabric, with an IP address range you choose.
Diagram
# Create a VPC with a specific CIDR block aws ec2 create-vpc \ --cidr-block 10.0.0.0/16 \ --tag-specifications 'ResourceType=vpc,Tags=[{Key=Name,Value=production-vpc}]' # Enable DNS hostnames and DNS resolution (needed for many # services, including private VPC endpoints later in this part) aws ec2 modify-vpc-attribute --vpc-id vpc-0123456789abcdef0 --enable-dns-hostnames aws ec2 modify-vpc-attribute --vpc-id vpc-0123456789abcdef0 --enable-dns-support # List all VPCs in the current region aws ec2 describe-vpcs --query 'Vpcs[].{ID:VpcId,CIDR:CidrBlock,Name:Tags[?Key==`Name`]|[0].Value}' --output table
Every AWS account gets one default VPC per region automatically, pre-configured with public subnets in every AZ — genuinely useful for quick experiments, but a real production workload should always live in a deliberately-designed, custom VPC, for the same reason a single shared AWS account is a trap (Part 1): the defaults optimize for "get started fast," not for the isolation and control a real system needs.
CIDR Blocks — Planning IP Address Space Correctly#
Getting VPC CIDR planning wrong is one of the most consequential, hardest-to-fix-later networking mistakes — worth taking seriously from day one.
Diagram
| CIDR suffix | Total addresses | AWS-usable addresses (5 reserved per subnet) |
|---|---|---|
/16 | 65,536 | Typical VPC size |
/24 | 256 | Typical subnet size |
/28 | 16 | 11 usable — smallest practical subnet |
Why AWS reserves exactly 5 IP addresses in EVERY subnet, worth knowing precisely for an interview: the network address, the VPC router address (+1), the DNS server address (+2), a reserved address for future use (+3), and the broadcast address (+last) — meaning a /24 subnet (256 addresses) actually has 251 usable addresses, not 256.
# A calculator worth internalizing, not memorizing a tool for: # usable hosts = 2^(32 - prefix) - 5 # /24 = 2^8 - 5 = 256 - 5 = 251 usable # /28 = 2^4 - 5 = 16 - 5 = 11 usable
The single most important, genuinely common real-world mistake worth naming explicitly and early: choosing a VPC CIDR block that OVERLAPS with another VPC you'll later need to connect (via peering, Part 4, or Transit Gateway). Two VPCs with overlapping CIDR ranges (e.g. both using 10.0.0.0/16) cannot be peered or connected via Transit Gateway at all — routing simply cannot distinguish which VPC an overlapping address belongs to. The fix has to happen at design time: maintain a company-wide CIDR allocation plan before creating VPCs, e.g. assigning each VPC (or account) a distinct /16 block out of a larger private range (10.0.0.0/8), so no two VPCs an organization might ever need to connect can overlap.
Subnets — Carving Up the VPC#
A subnet is a sub-range of the VPC's CIDR block, tied to exactly one Availability Zone.
Diagram
# Create a subnet within a VPC, in a specific AZ aws ec2 create-subnet \ --vpc-id vpc-0123456789abcdef0 \ --cidr-block 10.0.1.0/24 \ --availability-zone us-east-1a \ --tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=public-1a}]'
Why a subnet is tied to exactly one AZ, and why this directly drives multi-AZ design, worth stating explicitly: since Part 3's Auto Scaling Groups need to spread instances across multiple AZs for fault tolerance, and a subnet can only exist in one AZ, achieving that spread REQUIRES creating at least one subnet PER AZ you want to use — this is precisely why a real production VPC always has multiple subnets of the same "type" (e.g. three public subnets, one per AZ, not just one).
Public vs Private vs Isolated Subnets#
Public, private, and isolated are not AWS technical terms with a checkbox — they're a naming convention describing a subnet's ROUTE TABLE configuration (next section), worth understanding precisely rather than as magic labels.
Diagram
Why this three-tier pattern is the single most common, genuinely best-practice VPC design, worth stating explicitly as the default to reach for: it directly implements the "defense in depth" and "least exposure" principles already covered generically in the DevSecOps series — a database in an isolated subnet simply CANNOT be reached from the internet no matter how a security group is misconfigured, because the network layer itself provides no path there at all. This is a genuinely stronger guarantee than a security-group-only defense, since it removes an entire class of possible human error.
Route Tables — How Traffic Actually Finds Its Way#
A route table is a set of rules determining where network traffic from a subnet is directed, based on destination IP.
# Create a route table and associate it with a subnet aws ec2 create-route-table --vpc-id vpc-0123456789abcdef0 \ --tag-specifications 'ResourceType=route-table,Tags=[{Key=Name,Value=public-rt}]' aws ec2 associate-route-table \ --route-table-id rtb-0123456789abcdef0 \ --subnet-id subnet-0123456789abcdef0 # Add a route sending all internet-bound traffic (0.0.0.0/0) # to the Internet Gateway — this is literally what makes a # subnet "public" aws ec2 create-route \ --route-table-id rtb-0123456789abcdef0 \ --destination-cidr-block 0.0.0.0/0 \ --gateway-id igw-0123456789abcdef0 # View the full route table aws ec2 describe-route-tables --route-table-ids rtb-0123456789abcdef0 \ --query 'RouteTables[0].Routes' --output table
A precise, important rule worth memorizing: routing always follows the MOST SPECIFIC matching route ("longest prefix match") — a route table with both a specific route to 10.1.0.0/16 (e.g. via VPC peering) and a catch-all 0.0.0.0/0 route (e.g. via NAT Gateway) will send traffic destined for 10.1.5.20 through the specific peering route, not the catch-all, because /16 is more specific than /0.
The Internet Gateway#
An Internet Gateway (IGW) is a horizontally-scaled, highly-available AWS-managed component attached to a VPC, providing the actual path to and from the public internet.
aws ec2 create-internet-gateway --tag-specifications 'ResourceType=internet-gateway,Tags=[{Key=Name,Value=production-igw}]' aws ec2 attach-internet-gateway --internet-gateway-id igw-0123456789abcdef0 --vpc-id vpc-0123456789abcdef0
Worth stating precisely: an IGW by itself does nothing until a route table explicitly sends traffic to it (the 0.0.0.0/0 → igw-... route from the previous section) — a subnet is only "public" because of that route table entry, never because of the IGW's mere existence in the VPC.
NAT Gateway vs NAT Instance#
Both solve the same problem — letting a private subnet's resources initiate OUTBOUND internet connections (e.g. downloading OS updates, calling an external API) without being directly reachable FROM the internet — but with a real, worth-knowing tradeoff.
Diagram
| NAT Gateway (AWS-managed) | NAT Instance (self-managed EC2) | |
|---|---|---|
| Availability | Highly available within its AZ, AWS-managed | You manage HA yourself (often needs its own failover automation) |
| Throughput | Scales automatically up to very high bandwidth | Limited by the chosen instance type |
| Maintenance | Zero — fully managed | You patch and maintain the OS yourself |
| Cost | Per-hour + per-GB data processing charge | Just the EC2 instance cost (often cheaper at low/predictable volume) |
| Recommendation | The default choice for nearly all modern workloads | Rare edge cases with very specific, high-volume cost sensitivity |
# Create a NAT Gateway — must live in a PUBLIC subnet, # needs an Elastic IP aws ec2 allocate-address --domain vpc aws ec2 create-nat-gateway \ --subnet-id subnet-public-1a \ --allocation-id eipalloc-0123456789abcdef0 \ --tag-specifications 'ResourceType=natgateway,Tags=[{Key=Name,Value=nat-1a}]' # Route private subnet traffic through it aws ec2 create-route \ --route-table-id rtb-private-1a \ --destination-cidr-block 0.0.0.0/0 \ --nat-gateway-id nat-0123456789abcdef0
Why "one NAT Gateway per AZ" is the correct production pattern, worth stating explicitly, directly reusing the multi-AZ fault-tolerance principle already established: a single NAT Gateway shared across all AZs' private subnets means that NAT Gateway's AZ becomes a single point of failure for EVERY private subnet's outbound internet access, even ones in otherwise-healthy AZs — a genuinely common cost-cutting mistake that quietly reintroduces the exact single point of failure this whole series has been warning against. The real tradeoff: one NAT Gateway per AZ costs more (each has its own hourly charge) than one shared NAT Gateway, but provides genuine AZ-level fault isolation.
A Full Worked Three-Tier VPC#
Bringing everything together into one concrete, complete, production-realistic design.
Diagram
This is the direct, concrete AWS realization of the layered architecture ideas already covered across the Reliability & Architecture Patterns and Capacity Planning series — a public tier for entry points, a private tier for application logic, and an isolated tier for data, with each layer's blast radius contained by both routing (this part) and security groups (next section).
Security Groups — Stateful, Instance-Level Firewalls#
A Security Group (SG) acts as a virtual firewall attached to individual resources (an EC2 instance's network interface, an RDS instance, a Lambda function's ENI).
# Create a security group aws ec2 create-security-group \ --group-name app-sg \ --description "App server security group" \ --vpc-id vpc-0123456789abcdef0 # Allow inbound HTTPS ONLY from the load balancer's security group # (not a raw IP range — see "chaining" below) aws ec2 authorize-security-group-ingress \ --group-id sg-app123 \ --protocol tcp --port 443 \ --source-group sg-alb456 # View a security group's current rules aws ec2 describe-security-groups --group-ids sg-app123 \ --query 'SecurityGroups[0].{Inbound:IpPermissions,Outbound:IpPermissionsEgress}'
The single most important property of a Security Group, worth stating precisely: it is STATEFUL. If inbound traffic is allowed in, the RETURN traffic is automatically allowed out, without needing a matching outbound rule — the SG tracks the connection and permits its response automatically. Security groups are also allow-only — there is no explicit "Deny" rule type; anything not explicitly allowed is implicitly denied (an application, at the SG layer, of the same default-deny principle already seen for IAM in Part 2).
Network ACLs — Stateless, Subnet-Level Firewalls#
A Network ACL (NACL) operates at the SUBNET level (every resource in the subnet is subject to it), and — critically — is stateless.
# Create a custom NACL aws ec2 create-network-acl --vpc-id vpc-0123456789abcdef0 # NACL rules are ORDERED and numbered — lowest rule number # evaluated first, first match wins aws ec2 create-network-acl-entry \ --network-acl-id acl-0123456789abcdef0 \ --rule-number 100 --protocol tcp --port-range From=443,To=443 \ --cidr-block 0.0.0.0/0 --rule-action allow --ingress # Because NACLs are STATELESS, you must ALSO explicitly # allow the RETURN traffic on high, ephemeral ports outbound — # a genuinely common mistake if forgotten aws ec2 create-network-acl-entry \ --network-acl-id acl-0123456789abcdef0 \ --rule-number 100 --protocol tcp --port-range From=1024,To=65535 \ --cidr-block 0.0.0.0/0 --rule-action allow --egress
Why the statelessness matters so much in practice, worth explaining precisely: unlike a security group, a NACL does NOT automatically permit return traffic — an inbound allow rule on port 443 does not, by itself, let the RESPONSE traffic (which typically returns on a high, ephemeral port, not 443) leave the subnet, unless a separate, explicit outbound rule allows it. This is the single most common source of "my security group looks right but traffic still doesn't work" debugging sessions involving a custom NACL.
Security Groups vs NACLs — The Full Comparison#
A genuinely frequent, precise interview question — worth having every row of this table ready.
| Security Group | Network ACL | |
|---|---|---|
| Operates at | Instance/ENI level | Subnet level |
| State | Stateful (return traffic auto-allowed) | Stateless (return traffic needs its own explicit rule) |
| Rule types | Allow only | Allow AND explicit Deny |
| Rule evaluation | ALL rules evaluated; if any matches, allowed | Rules evaluated IN ORDER by rule number; FIRST match wins |
| Default behavior | Deny all inbound, allow all outbound (default SG) | Default NACL allows everything; a CUSTOM NACL denies everything until rules added |
| Applies to | Just the resources explicitly attached to it | EVERY resource in the associated subnet, automatically |
Why using BOTH together is genuinely good defense-in-depth, worth stating explicitly rather than treating them as redundant: security groups provide fine-grained, resource-specific allow rules as the PRIMARY control; NACLs provide a coarser, subnet-wide backstop — most notably, a NACL's explicit DENY capability is useful for quickly, subnet-wide blocking a specific known-bad IP range without needing to touch every individual security group.
Security Group Chaining — Referencing Other Groups#
A genuinely powerful, underused technique already shown briefly above — worth its own explicit callout.
Diagram
Why referencing a security group ID (instead of a hardcoded IP/CIDR range) is the single strongest, most maintainable security-group pattern, worth stating explicitly: it automatically stays correct as instances scale in and out of an Auto Scaling Group (Part 3) — new instances get their IP dynamically, but as long as they're in the referenced SG, the chained rule keeps working with zero manual updates, unlike a CIDR-based rule which would need constant, error-prone manual maintenance as the fleet changes. This directly implements the "who can talk to whom" question as an identity-based rule rather than a location-based one — a network-layer parallel to the identity-based IAM policies from Part 2.
VPC Peering#
VPC Peering creates a direct, private network connection between exactly two VPCs, as if they were on the same network.
# Request a peering connection (from the "requester" VPC) aws ec2 create-vpc-peering-connection \ --vpc-id vpc-requester123 \ --peer-vpc-id vpc-accepter456 \ --peer-region us-west-2 # The OTHER side (or account) must explicitly accept it aws ec2 accept-vpc-peering-connection --vpc-peering-connection-id pcx-0123456789abcdef0 # Peering does NOT automatically route traffic — you still need # explicit routes on BOTH sides pointing to the peering connection aws ec2 create-route \ --route-table-id rtb-vpcA \ --destination-cidr-block 10.1.0.0/16 \ --vpc-peering-connection-id pcx-0123456789abcdef0
A genuinely important limitation worth naming precisely: VPC Peering is NOT transitive. If VPC A peers with VPC B, and VPC B peers with VPC C, traffic from A cannot reach C through B — each peering connection is a direct, point-to-point link only. This limitation is exactly why Transit Gateway (next section) exists.
Diagram
Transit Gateway — Solving the Peering-Mesh Problem#
As the number of VPCs needing to interconnect grows, pairwise VPC Peering becomes an unmanageable N² mesh of connections.
Diagram
Diagram
Why this matters at real scale, worth stating with the actual math: 4 VPCs need 6 pairwise peering connections to fully interconnect (N×(N-1)/2); 10 VPCs need 45; 50 VPCs need 1,225 — an obviously unmanageable, error-prone growth curve. A Transit Gateway turns this into a simple hub-and-spoke model: each VPC attaches to the Transit Gateway ONCE, and the Transit Gateway's own route tables control which spokes can reach which — genuinely the same "avoid an unmanageable N² mesh" lesson already seen for service-to-service communication in the Kubernetes Deep Dive series' service mesh discussion, just applied at the network layer instead of the application layer.
# Create a Transit Gateway aws ec2 create-transit-gateway --description "production-tgw" # Attach a VPC to it aws ec2 create-transit-gateway-vpc-attachment \ --transit-gateway-id tgw-0123456789abcdef0 \ --vpc-id vpc-0123456789abcdef0 \ --subnet-ids subnet-aaa subnet-bbb subnet-ccc # Route traffic from a VPC's route table to the Transit Gateway aws ec2 create-route \ --route-table-id rtb-vpc1 \ --destination-cidr-block 10.0.0.0/8 \ --transit-gateway-id tgw-0123456789abcdef0
Transit Gateway also natively supports Direct Connect and Site-to-Site VPN attachments (both covered shortly), making it the standard, central connectivity hub in a real multi-VPC, multi-account landing zone (directly extending the shared-networking account pattern from Part 1).
VPC Endpoints — Gateway Endpoints#
A genuinely important, security-relevant pattern: reaching AWS services (like S3 or DynamoDB) from a private subnet WITHOUT routing that traffic out through a NAT Gateway and across the public internet at all.
Diagram
Gateway Endpoints exist for exactly two services — S3 and DynamoDB — added as a special route table entry, free of charge.
aws ec2 create-vpc-endpoint \ --vpc-id vpc-0123456789abcdef0 \ --service-name com.amazonaws.us-east-1.s3 \ --route-table-ids rtb-private-1a rtb-private-1b \ --vpc-endpoint-type Gateway
Why this is a genuinely strong, easy security and cost win worth adopting as a default, not an optional optimization: it eliminates an entire class of accidental data-exfiltration risk (S3 traffic literally cannot cross into the public internet at all through this path), while also reducing NAT Gateway data-processing costs for what's often a very high-traffic destination.
VPC Endpoints — Interface Endpoints and PrivateLink#
For nearly every OTHER AWS service (not S3/DynamoDB), the equivalent is an Interface Endpoint — powered by AWS PrivateLink — which places an actual elastic network interface (ENI), with a private IP, directly inside your subnet.
aws ec2 create-vpc-endpoint \ --vpc-id vpc-0123456789abcdef0 \ --service-name com.amazonaws.us-east-1.secretsmanager \ --vpc-endpoint-type Interface \ --subnet-ids subnet-private-1a subnet-private-1b \ --security-group-ids sg-endpoint123
| Gateway Endpoint | Interface Endpoint (PrivateLink) | |
|---|---|---|
| Supported services | S3, DynamoDB only | Nearly every other AWS service, plus third-party/custom SaaS endpoints |
| How it works | A route table entry | An actual ENI with a private IP inside your subnet |
| Cost | Free | Small hourly + per-GB charge |
| Security control | Endpoint policy | Endpoint policy AND security group (since it's a real ENI) |
PrivateLink's most powerful use case, worth knowing explicitly: it's not limited to AWS's own services — a SaaS vendor (or another team's service in a completely different AWS account) can expose their own service as a PrivateLink endpoint, letting your VPC reach it privately without any VPC peering, without any exposure to the public internet, and without the vendor ever needing visibility into your network at all. This is genuinely the AWS-native realization of a "service mesh"-style private service exposure, without needing an actual service mesh (Kubernetes Deep Dive series, Part 4).
Site-to-Site VPN#
A Site-to-Site VPN connects an on-premises network to a VPC over an encrypted tunnel, riding over the public internet.
Diagram
# Create a Customer Gateway (represents your on-prem router) aws ec2 create-customer-gateway \ --type ipsec.1 --public-ip 203.0.113.10 --bgp-asn 65000 # Create a Virtual Private Gateway (the AWS side) and attach it aws ec2 create-vpn-gateway --type ipsec.1 aws ec2 attach-vpn-gateway --vpn-gateway-id vgw-0123456789abcdef0 --vpc-id vpc-0123456789abcdef0 # Create the actual VPN connection between them aws ec2 create-vpn-connection \ --type ipsec.1 \ --customer-gateway-id cgw-0123456789abcdef0 \ --vpn-gateway-id vgw-0123456789abcdef0
Setup time: typically minutes to hours. Cost: relatively low, pay-per-connection-hour. The real tradeoff worth stating precisely: since it rides over the public internet, both bandwidth and latency are variable and not guaranteed — genuinely fine for many workloads, but not for latency-sensitive or very high-throughput hybrid architectures, which is exactly where Direct Connect becomes the better fit.
AWS Direct Connect#
A dedicated, private physical network connection from an on-premises data center directly into AWS's network, bypassing the public internet entirely.
Diagram
Setup time: weeks to months (it requires physically provisioning a cross-connect at a real Direct Connect location). Cost: meaningfully higher fixed cost, but predictable, consistent bandwidth and latency, with none of the public-internet variability a VPN carries. A genuinely important nuance worth knowing: Direct Connect traffic is private (bypassing the public internet) but is not automatically encrypted — many organizations layer a VPN on top of Direct Connect (called "Direct Connect plus VPN") specifically when both dedicated bandwidth AND encryption in transit are required.
Choosing Between VPN, Direct Connect, and the Public Internet#
Diagram
VPC Flow Logs — Seeing What's Actually Happening#
VPC Flow Logs capture metadata about the IP traffic flowing through a VPC's network interfaces — not the actual packet contents, but the "who talked to whom, on what port, how much data, and was it accepted or rejected."
# Enable flow logs for a VPC, sent to CloudWatch Logs aws ec2 create-flow-logs \ --resource-type VPC \ --resource-ids vpc-0123456789abcdef0 \ --traffic-type ALL \ --log-destination-type cloud-watch-logs \ --log-group-name /vpc/flow-logs \ --deliver-logs-permission-arn arn:aws:iam::123456789012:role/FlowLogsRole # A flow log record looks like this (simplified): # version account-id interface-id srcaddr dstaddr srcport dstport # protocol packets bytes start end action log-status # 2 123456789012 eni-abc 10.0.1.5 10.0.11.20 443 51820 6 10 840 ... ACCEPT OK
Why Flow Logs are the single most important tool for diagnosing "why can't service A reach service B" networking incidents, worth stating explicitly: they show DEFINITIVELY whether traffic was REJECTED (a security group or NACL is blocking it) or never arrived at all (a routing problem) — this is the network-layer equivalent of the tcpdump diagnostic tool already covered in the Linux & Networking series' troubleshooting toolkit, just captured continuously, at the VPC level, for exactly this kind of retrospective investigation.
AWS Network Firewall#
For traffic-inspection needs beyond what security groups and NACLs provide (which only filter on IP/port/protocol), AWS Network Firewall offers stateful, deep-packet-inspection-capable filtering — including domain-name-based rules, intrusion detection/prevention signatures, and TLS inspection.
Diagram
When to reach for it, worth stating precisely as a decision, not a default: Network Firewall is genuinely useful for compliance-driven requirements (e.g. "outbound traffic may only reach an explicit allowlist of domains") or intrusion-detection needs that security groups and NACLs structurally cannot express (they only understand IP/port/protocol, never domain names or packet payload content) — for most workloads without those specific requirements, security groups plus NACLs remain sufficient and simpler.
VPC Lattice — A Newer, Application-Layer Alternative#
Worth knowing about as a genuinely newer AWS service, since it represents a meaningfully different approach to the same "many services need to talk to each other, across VPCs and accounts" problem that PrivateLink, Peering, and Transit Gateway all address at the NETWORK layer. VPC Lattice instead operates at the APPLICATION layer — closer conceptually to the service mesh discussion already covered in the Kubernetes Deep Dive series (Part 4) than to traditional VPC networking.
Diagram
# Create a service network — the logical grouping services join aws vpc-lattice create-service-network --name production-services # Create a Lattice "service" (can front EC2, ECS, Lambda, or an IP target) aws vpc-lattice create-service --name payments-service # Associate the service network with a VPC — this is what # actually enables connectivity, similar in spirit to a # Transit Gateway VPC attachment, but at the app layer aws vpc-lattice create-service-network-vpc-association \ --service-network-identifier sn-0123456789abcdef0 \ --vpc-identifier vpc-0123456789abcdef0
Why this is worth knowing as a genuinely different tool from everything else in this part, worth stating the distinction precisely: Transit Gateway, Peering, and PrivateLink all operate at the NETWORK layer — they control IP-level reachability. VPC Lattice operates at the APPLICATION layer — it understands HTTP/HTTPS/gRPC routing, applies access policies per-service (using IAM, Part 2) rather than per-IP, and works UNIFORMLY across compute platforms that don't even share the same underlying networking model (a Lambda function has no VPC ENI by default, for instance, yet can still join a Lattice service network). It's a genuinely compelling option specifically for organizations running a heterogeneous mix of EC2, containers (Part 7), and serverless services that all need consistent, centrally-governed service-to-service access control — without needing to run and operate a full Kubernetes-based service mesh to get it.
PrivateLink vs VPC Peering vs Transit Gateway — Choosing the Right Connectivity#
A genuinely common, real architectural decision worth having a clear framework for.
| Need | Best fit |
|---|---|
| Two VPCs, full bidirectional network reachability, small number of VPCs | VPC Peering |
| Many VPCs needing to interconnect, or hybrid on-prem connectivity via VPN/Direct Connect too | Transit Gateway |
| One-directional access to a SPECIFIC service (not the whole VPC), possibly across organizational/vendor boundaries | PrivateLink (Interface Endpoint) |
| Reaching only S3 or DynamoDB from a private subnet | Gateway Endpoint |
Why PrivateLink is the right choice specifically when you want to expose ONE service without exposing your whole network, worth stating explicitly: VPC Peering and Transit Gateway both create full network-level reachability between the connected VPCs (subject to route tables and security groups) — PrivateLink instead exposes exactly one specific service endpoint, and the consumer VPC gets no visibility into or reachability toward anything else in the provider's VPC at all. This is a meaningfully stronger form of least-privilege network exposure, directly connecting to the least-privilege theme carried throughout this entire series.
DNS Resolution Inside a VPC#
Every VPC automatically gets a built-in DNS resolver (at the base of the VPC's CIDR range plus two, e.g. 10.0.0.2 for a 10.0.0.0/16 VPC), resolving both public DNS and AWS-internal private DNS names (e.g. an RDS endpoint, or a PrivateLink interface endpoint's private DNS name).
# The VPC's own resolver is always at the ".2" address # of the VPC's CIDR range dig @10.0.0.2 my-rds-instance.abc123.us-east-1.rds.amazonaws.com # Route 53 Resolver can also forward specific domains to # on-premises DNS servers (hybrid DNS) — genuinely useful # alongside Site-to-Site VPN/Direct Connect aws route53resolver create-resolver-endpoint \ --creator-request-id "$(uuidgen)" \ --security-group-ids sg-resolver123 \ --direction OUTBOUND \ --ip-addresses SubnetId=subnet-private-1a SubnetId=subnet-private-1b
A full treatment of Route 53 (public/private hosted zones, routing policies) is covered in Part 8 alongside load balancing and CDN, since DNS-based traffic routing is most naturally understood together with those.
IPv6 in AWS — A Brief, Practical Note#
AWS VPCs support dual-stack (IPv4 + IPv6 simultaneously) configurations. Worth knowing one genuinely important, frequently-surprising fact: AWS does not support IPv6-only NAT in the traditional sense — IPv6 addresses assigned within a VPC are, by default, globally routable and public; achieving "private" IPv6 behavior requires an Egress-Only Internet Gateway (the IPv6 analog of a NAT Gateway — allows outbound-only IPv6 internet access without inbound reachability), rather than reusing the IPv4 NAT Gateway concept directly.
aws ec2 create-egress-only-internet-gateway --vpc-id vpc-0123456789abcdef0
For most workloads today, IPv4 (with the public/private/isolated subnet pattern already covered) remains the default, pragmatic choice — IPv6 adoption is growing but still often opt-in for specific compliance or address-exhaustion-driven reasons.
IPAM — IP Address Manager#
As an organization's landing zone (Part 1) grows to dozens or hundreds of VPCs, the CIDR-planning discipline this part opened with — "never let two VPCs that might connect later overlap" — becomes genuinely hard to track by hand in a spreadsheet. AWS IPAM (IP Address Manager) is AWS's own tool for centrally planning, tracking, and auditing IP address usage across an entire Organization.
Diagram
# Create an IPAM and a top-level pool aws ec2 create-ipam --description "org-wide-ipam" aws ec2 create-ipam-pool \ --ipam-scope-id ipam-scope-0123456789abcdef0 \ --address-family ipv4 \ --description "production-pool" # Provision a CIDR range into the pool aws ec2 provision-ipam-pool-cidr \ --ipam-pool-id ipam-pool-0123456789abcdef0 \ --cidr 10.0.0.0/8 # Allocate a VPC's CIDR block FROM the pool, instead of picking # one by hand — IPAM guarantees it cannot overlap with any other # allocation already made from the same pool aws ec2 create-vpc \ --ipv4-ipam-pool-id ipam-pool-0123456789abcdef0 \ --ipv4-netmask-length 16
Why this converts CIDR planning from "a shared spreadsheet someone has to remember to update" into a structurally enforced guarantee, worth stating explicitly: once VPCs are created by requesting a block FROM an IPAM pool rather than specifying a raw CIDR by hand, overlapping allocations become genuinely impossible by construction — directly closing the exact failure mode described in this part's very first worked practice problem. IPAM also provides org-wide visibility into utilization — a genuinely useful input to the multi-account landing zone governance already covered in Part 1.
VPC Sharing via AWS Resource Access Manager (RAM)#
A less commonly known but genuinely useful pattern: instead of every account building and maintaining its OWN VPC, VPC Sharing lets a central "network" account own the actual VPC and subnets, while OTHER accounts deploy resources directly INTO those shared subnets.
Diagram
# From the shared-networking account: share a subnet with # specific other accounts (or the whole Organization) aws ram create-resource-share \ --name "shared-app-subnets" \ --resource-arns arn:aws:ec2:us-east-1:111122223333:subnet/subnet-0123456789abcdef0 \ --principals 444455556666 555566667777
Why this is worth knowing as a genuinely important alternative to VPC Peering/Transit Gateway for the specific "many accounts, one shared network" case, worth stating explicitly: it avoids creating N separate VPCs (and N sets of NAT Gateways, route tables, and CIDR allocations) entirely — application teams get their own AWS account for IAM/billing isolation (Part 1's core benefit), while still sharing ONE centrally-managed network, reducing both cost (fewer NAT Gateways) and networking complexity (no peering/Transit Gateway mesh needed between these accounts at all, since they're technically all in the same VPC). The tradeoff, worth stating honestly: application teams give up the ability to design their OWN network topology — they're bound by whatever the shared-networking account's VPC design already provides.
VPC Reachability Analyzer — Automated Path Diagnosis#
A genuinely powerful, less commonly known diagnostic tool worth knowing precisely: instead of manually tracing through route tables, security groups, and NACLs by hand during an incident, Reachability Analyzer does it automatically, hop by hop.
# Ask: can this specific EC2 instance reach this specific # RDS database, on port 3306? aws ec2 create-network-insights-path \ --source i-0123456789abcdef0 \ --destination db-0123456789abcdef0 \ --protocol tcp --destination-port 3306 aws ec2 start-network-insights-analysis \ --network-insights-path-id nip-0123456789abcdef0 # The result shows EXACTLY where the path breaks, if it does — # e.g. "blocked by security group sg-abc123, rule denying port 3306" aws ec2 describe-network-insights-analyses \ --network-insights-analysis-ids nia-0123456789abcdef0 \ --query 'NetworkInsightsAnalyses[0].{Reachable:NetworkPathFound,Explanations:Explanations}'
Why this is worth reaching for BEFORE manually digging through Flow Logs during an incident, worth stating explicitly, directly connecting to the Incident Management series' emphasis on fast diagnosis: Reachability Analyzer evaluates the ENTIRE path — route tables, security groups, NACLs, and even VPC Peering/Transit Gateway routing — in seconds, and names the EXACT component blocking traffic, turning what could be a 20-minute manual trace through five different consoles into a single API call with a definitive answer.
AWS Global Accelerator — A Brief, Practical Note#
Worth distinguishing precisely from CloudFront (covered in Part 8): Global Accelerator improves performance and availability for NON-HTTP(S) or latency-sensitive TCP/UDP traffic by routing it onto AWS's own private global network backbone as early as possible, rather than the public internet.
Diagram
Why this is a meaningfully different tool from CloudFront, worth stating the distinction precisely: CloudFront is a CACHING CDN, optimized for HTTP(S) content that benefits from being cached at the edge; Global Accelerator does NOT cache anything — it simply routes traffic onto AWS's private backbone sooner, which helps ANY TCP/UDP traffic (including non-cacheable, non-HTTP protocols like gaming or VoIP traffic) by reducing the portion of the trip that crosses the unpredictable public internet. It's also a genuinely useful tool for fast, near-instant regional failover — Global Accelerator can shift traffic to a healthy region using static anycast IPs that never change, avoiding the DNS TTL propagation delay inherent in a Route 53-based failover (covered in Part 8).
A Networking Incident Troubleshooting Playbook#
Bringing every diagnostic tool from this part together into one concrete, ordered playbook — worth having memorized for exactly the kind of "service A can't reach service B" incident this whole part has been building toward.
Diagram
Why starting with Reachability Analyzer specifically, rather than jumping straight to Flow Logs, is the more efficient order, worth stating explicitly: Reachability Analyzer answers the NETWORK-layer question (can traffic physically get there at all) in seconds, definitively — Flow Logs are more valuable for the SECOND question (traffic IS arriving, but something at the application layer is rejecting it), so checking network-layer reachability first avoids wasting time manually correlating Flow Log entries for a problem that a single API call could have already ruled out.
Networking Best Practices — The Consolidated Checklist#
A dense, exam-and-interview-ready summary of everything in this part, worth keeping as a standing reference.
- Plan CIDR ranges org-wide BEFORE creating VPCs — never let two VPCs that might need to connect later share overlapping address space.
- Always use the three-tier subnet pattern (public / private / isolated) spread across at least 3 AZs — never put a database directly in a public subnet.
- One NAT Gateway per AZ, not one shared NAT Gateway for the whole VPC — a shared NAT Gateway reintroduces a single point of failure.
- Prefer security-group-to-security-group references over hardcoded CIDR blocks in ingress rules — they stay correct automatically as fleets scale.
- Use Gateway Endpoints for S3/DynamoDB, and Interface Endpoints (PrivateLink) for other AWS services accessed from private subnets — keeps traffic off the public internet and reduces NAT costs.
- Enable VPC Flow Logs on every production VPC by default — the cost is low, and it's frequently the ONLY way to retroactively diagnose a connectivity or security incident.
- Use Transit Gateway, not a growing mesh of VPC Peering connections, once an organization has more than a handful of VPCs needing to interconnect.
- Choose Direct Connect over VPN only when sustained, predictable, high-volume bandwidth genuinely justifies the cost and lead time — otherwise VPN is faster to stand up and more cost-effective.
- Treat NACLs as a coarse, subnet-wide backstop, not the primary access control — security groups should carry the main day-to-day access logic, since they're stateful and far less error-prone to maintain correctly.
Common Mistakes#
| Mistake | Why It's Wrong | Fix |
|---|---|---|
| Creating multiple VPCs with the same or overlapping CIDR block | Overlapping VPCs can never be peered or Transit-Gateway-connected — routing can't distinguish them | Maintain an org-wide CIDR allocation plan before any VPC is created |
| Placing a database directly in a public subnet "because it's simpler" | Removes an entire layer of network-level protection that security groups alone can't fully replace | Use the three-tier pattern — databases belong in isolated subnets |
| Sharing a single NAT Gateway across all AZs to save cost | Reintroduces a single point of failure for outbound internet access across the entire VPC | Deploy one NAT Gateway per AZ for genuine fault isolation |
| Forgetting that NACLs are stateless when adding a custom NACL rule | Return traffic silently gets dropped without an explicit outbound rule for ephemeral ports | Always pair inbound NACL allow rules with the necessary outbound return-traffic rules |
| Assuming VPC Peering is transitive | Traffic cannot hop through an intermediate peered VPC to reach a third VPC | Use Transit Gateway for any topology beyond simple, direct pairwise connections |
| Routing all AWS-service traffic (e.g. to S3) through a NAT Gateway by default | Unnecessary NAT data-processing cost, and traffic technically transits the public internet path | Use Gateway/Interface VPC Endpoints for AWS service traffic from private subnets |
| Running production without VPC Flow Logs enabled | Removes the single most useful tool for retroactively diagnosing a network-layer incident | Enable Flow Logs by default on every production VPC |
Worked Practice Problems#
Problem 1: Two teams each independently create a VPC using the default 10.0.0.0/16 CIDR block. Eighteen months later, a new project requires connecting both VPCs via VPC Peering, and the request fails. What's the root cause, and what should have prevented this?
Answer: Both VPCs use the identical CIDR block, and AWS cannot establish a VPC Peering connection (or a Transit Gateway attachment) between VPCs with overlapping address space, since routing has no way to distinguish which VPC a given destination IP within 10.0.0.0/16 actually belongs to. This should have been prevented with an organization-wide CIDR allocation plan established BEFORE either VPC was created — assigning each VPC (or account) its own distinct, non-overlapping block from a larger private range, exactly as this tutorial recommends. At this point, the only fix is a genuinely painful VPC re-IP migration for one of the two VPCs, underscoring why this decision needs to be made correctly up front.
Problem 2: An application in a private subnet reports intermittent failures connecting to an external third-party API, and a review reveals the VPC has only one NAT Gateway, located in AZ us-east-1a, while the application's Auto Scaling Group spans three AZs. What's the likely failure mode, and what's the architectural fix?
Answer: Instances in us-east-1b and us-east-1c are routing their outbound internet traffic through the single NAT Gateway in us-east-1a — this works under normal conditions but means that any disruption affecting us-east-1a specifically (or even just cross-AZ data transfer costs and added latency for the non-1a instances) creates a single point of failure and degraded reliability for two-thirds of the fleet, exactly the kind of hidden single point of failure this tutorial warns against. The fix is deploying one NAT Gateway per AZ, each routed to only by that AZ's own private subnet route table — restoring genuine AZ-level fault isolation matching the Auto Scaling Group's own multi-AZ design.
Problem 3: A security team wants to guarantee that traffic from an application's private subnet to Amazon S3 never traverses the public internet, for compliance reasons, without adding a NAT Gateway dependency for this specific traffic. What AWS networking feature satisfies this exactly, and why does it work for S3 specifically?
Answer: An S3 Gateway VPC Endpoint. Because it's implemented as a special route table entry rather than a network appliance, traffic destined for S3 is routed directly within AWS's own private network fabric — it never traverses a NAT Gateway, an Internet Gateway, or the public internet at any point, directly satisfying the compliance requirement. It's available specifically for S3 and DynamoDB (Gateway Endpoints); for any other AWS service with the same requirement, the equivalent would be an Interface Endpoint (PrivateLink) instead, since Gateway Endpoints don't extend to other services.
Problem 4: During an incident, an application team reports "our service can't reach the payments service," and an engineer spends 25 minutes manually tracing through route tables, security group rules, and NACL rules across two VPCs connected via Transit Gateway before finding the actual cause — an overly narrow security group rule on the payments service. What tool, used first, would likely have cut this diagnosis time dramatically, and why?
Answer: VPC Reachability Analyzer, run between the calling service's ENI and the payments service's ENI on the relevant port, before any manual tracing began. Reachability Analyzer automatically evaluates the entire path — route tables, security groups, NACLs, and Transit Gateway routing — in a single API call, and names the exact blocking component directly in its output, rather than requiring an engineer to manually reconstruct that same evaluation logic by hand across multiple consoles and two VPCs. Using it as the FIRST diagnostic step (per this part's troubleshooting playbook), rather than a last resort, would likely have identified the exact overly-narrow security group rule within seconds instead of the 25 minutes the manual approach took.
Problem 5: An organization with 40 AWS accounts, each currently running its own independent VPC with its own NAT Gateways, wants to reduce both networking cost and CIDR-planning overhead, without giving up per-team account-level IAM and billing isolation. What AWS feature directly addresses this, and what's the honest tradeoff involved?
Answer: VPC Sharing via AWS Resource Access Manager, centralizing the actual VPC, subnets, and NAT Gateways in one dedicated shared-networking account, while the 40 application teams continue deploying their resources (EC2, RDS, etc.) directly into the shared subnets from their own separate accounts — preserving each team's IAM and billing isolation while eliminating 39 redundant sets of NAT Gateways, route tables, and CIDR allocations. The honest tradeoff worth stating explicitly: application teams give up control over their own network topology entirely — subnet sizing, route table design, and NAT Gateway placement are now owned centrally by the shared-networking account, which is a real loss of autonomy some teams with genuinely unusual networking needs may find limiting.
Summary and What's Next#
- A VPC is your isolated private network in AWS; CIDR planning must happen organization-wide, up front — overlapping VPCs can never later be connected via peering or Transit Gateway.
- The public/private/isolated subnet pattern, spread across multiple AZs, is the standard production design — driven entirely by each subnet's route table configuration, not a special flag.
- NAT Gateways should be deployed one-per-AZ in production to avoid reintroducing a single point of failure; Internet Gateways provide the actual internet path for public subnets.
- Security Groups (stateful, instance-level, allow-only) and NACLs (stateless, subnet-level, allow+deny) serve complementary roles — chaining security groups by reference (not CIDR) is the strongest, most maintainable pattern.
- VPC Peering is not transitive; Transit Gateway solves the resulting N² mesh problem for organizations with many interconnected VPCs.
- Gateway Endpoints (S3/DynamoDB, free) and Interface Endpoints/PrivateLink (everything else, small cost) keep AWS-service traffic off the public internet entirely — a strong default, not just an optimization.
- Site-to-Site VPN (fast to set up, internet-based, variable performance) and Direct Connect (dedicated, predictable, longer lead time) are the two hybrid-connectivity options — chosen based on bandwidth, latency, and lead-time requirements.
- VPC Flow Logs are the single most valuable tool for retroactively diagnosing network-layer incidents — enable them on every production VPC by default.
Continue to Part 5 (05-storage-s3-ebs-efs.md) for a deep dive into AWS's storage services — S3, EBS, and EFS — now that the network fabric connecting them is fully understood.