Networking & VPC
A Virtual Private Cloud is your own isolated network on AWS. Everything you deploy lives inside one, and understanding VPC design is fundamental to building secure, reliable systems.
Introduction
When you launch an EC2 instance, a Lambda function, or an RDS database, it lives inside a VPC. A VPC is a logically isolated section of the AWS network where you control IP addressing, routing, and access control. Every AWS account comes with a default VPC, but for production workloads you will always create a custom one with a deliberate public/private subnet design.
Think of the five building blocks in layers: the VPC is the boundary that isolates your network from all other AWS customers, subnets are the floor plan dividing that space into public and private zones, route tables are the traffic signs that decide where each packet goes, gateways are the on/off ramps to the internet, and Security Groups plus NACLs are the locks on the doors. Together, these five concepts compose every real production architecture you will build on AWS.
Quick Navigation
1. VPC, Subnets, and CIDR
IP layout and address planning
2. Gateways, NAT, and Elastic IPs
Internet access, NAT options, and static IPs
3. Route Tables
How traffic decides where to go
4. Security Groups vs. Network ACLs
Stateful and stateless firewalls
5. Building a VPC via CLI
End-to-end build script
6. Observability & Advanced Features
Flow Logs, Reachability Analyzer, and more
7. VPC Peering and Endpoints
Connecting VPCs and reaching AWS services privately
1. VPC, Subnets, and CIDR
What is a VPC?
A Virtual Private Cloud is a logically isolated network provisioned within the AWS infrastructure. It is your own private section of the AWS cloud where you define the IP address space, create subnets, configure routing, and control what can enter and exit. No other AWS customer can see or reach resources inside your VPC unless you explicitly allow it.
What is a Subnet?
A subnet is a range of IP addresses carved out of your VPC's address space. Every resource you launch (EC2, RDS, Lambda, etc.) is placed in a subnet and receives an IP from that range. Subnets exist within a single Availability Zone. By splitting your VPC into multiple subnets across multiple AZs you gain both isolation and fault tolerance.
What is CIDR?
CIDR (Classless Inter-Domain Routing) is the notation used to express IP address ranges. The number after the slash is the prefix length: /16 means the first 16 bits are fixed, leaving 16 bits for hosts (65,536 addresses). /24 leaves 8 bits (256 addresses). The smaller the number, the larger the block. VPCs typically use /16 and subnets use /24.
Standard VPC Layout: Public and Private Subnets Across Two AZs
Internet-facing resources go in public subnets; app and database tiers stay in private subnets
CIDR Blocks
Your VPC gets an IP range from the RFC 1918 private address space, such as 10.0.0.0/16 (65,536 addresses). Subnets carve slices from it: /24 gives 256 addresses per subnet, but only 251 are usable because AWS reserves 5 per subnet for the network address, VPC router, DNS resolver, future use, and broadcast.
Public Subnets
A subnet is public when its route table has a 0.0.0.0/0 → igw-xxx route pointing to an Internet Gateway. There is no intrinsic "public" type in the AWS API. Resources also need Auto-assign public IPv4 enabled or an Elastic IP to be reachable. Use for load balancers, NAT Gateways, and bastion hosts.
Private Subnets
Private subnets have no route to an Internet Gateway, so instances cannot be reached from the internet. Outbound internet access (for package installs or API calls) routes through a NAT Gateway in a public subnet. EC2 app servers, ECS tasks, Lambda in VPC, RDS, and ElastiCache all belong here.
2. Gateways, NAT, and Elastic IPs
Internet Gateway (IGW)
An IGW is attached to the VPC itself, not to a subnet. It performs 1:1 NAT between an instance's private IP and its public IP (or Elastic IP). The IGW is horizontally scaled, redundant, and highly available by design, you never manage capacity for it, and each VPC can have only one.
The IGW alone does not route traffic. Three things must all be true for a resource to be reachable from the internet: the subnet's route table must have a 0.0.0.0/0 → igw-xxx route, the resource must have a public IPv4 address or Elastic IP, and security group rules must allow the traffic.
Free (no per-hour charge).
NAT Gateway
A NAT Gateway is a managed AWS service deployed in a specific public subnet. Private subnet route tables point outbound traffic to it: 0.0.0.0/0 → nat-xxx. Private instances can pull packages and reach external APIs but are never reachable from the internet.
For high availability, deploy one NAT Gateway per AZ. Private subnets in AZ-a should route through the NAT Gateway in AZ-a, not AZ-b. This avoids cross-AZ data transfer charges and limits the blast radius if one AZ fails. The NAT Gateway itself has no security group.
Charged per hour + per GB processed. Use one per AZ for HA.
NAT Instances: The Legacy Alternative
What is a NAT Instance?
Before NAT Gateway existed, the only way to give private instances outbound internet access was to launch a regular EC2 instance in a public subnet, enable source/destination check disabling on its ENI (so it would forward packets not addressed to itself), and install NAT software on it. Private subnet route tables then pointed 0.0.0.0/0 at that instance's ENI.
Any EC2 instance can technically act as a NAT instance. AWS used to maintain community AMIs (amzn-ami-vpc-nat-*) pre-configured with iptables rules, but these are no longer recommended for new workloads.
Why NAT Gateway Wins
| Aspect | NAT Instance | NAT Gateway |
|---|---|---|
| Availability | Manual failover required | Managed HA, multi-AZ |
| Bandwidth | Limited by instance type | Scales to 100 Gbps |
| Maintenance | You patch the OS | Fully managed by AWS |
| Security groups | Required | Not applicable |
| Cost model | EC2 hourly rate | Per hour + per GB |
| Performance | Unpredictable under load | Consistent |
Elastic IP Addresses
An Elastic IP (EIP) is a static public IPv4 address allocated to your AWS account and held until you explicitly release it. Unlike the auto-assigned public IP that an EC2 instance gets at launch (which changes every time the instance stops and restarts), an Elastic IP persists across stops, restarts, and even instance replacements. You associate an EIP with a specific instance or ENI. If that instance fails, you can remap the EIP to a replacement instance in seconds, keeping the same public IP address and avoiding DNS propagation delays. NAT Gateways always require one EIP, which is assigned at creation time (as seen in the CLI section).
Common uses
- NAT Gateways (mandatory)
- Bastion / jump hosts with a fixed IP
- Services that external partners whitelist by IP
Pricing gotcha
AWS charges for EIPs that are allocated but not associated with a running instance. Release EIPs you are not using to avoid unexpected charges.
Limit
Default quota is 5 EIPs per region per account. Request a quota increase via Service Quotas if you need more.
3. Route Tables
What is a Route Table?
A route table is a set of rules (routes) that the VPC router consults to decide where to send network traffic originating from a subnet. Every subnet must be associated with exactly one route table at a time. The VPC router itself is invisible, it is a logical construct that sits at the first usable IP of each subnet (e.g., 10.0.1.1 for 10.0.1.0/24) and forwards packets based on the route table.
Every VPC has a main route table created automatically. Any subnet not explicitly associated with a custom route table falls back to the main route table. The recommended practice is to keep the main route table private (no IGW route) and create separate custom route tables for public subnets. That way, a misconfigured subnet association cannot accidentally expose private resources to the internet.
Route Tables: Associations and Destinations
Public route tables route internet traffic to the IGW; private route tables route it to the NAT Gateway
Public Route Table Entries
Every route table automatically contains a local route: 10.0.0.0/16 → local. This route cannot be deleted and ensures all resources within the VPC can communicate with each other regardless of subnet. The most specific route wins, so VPC-internal traffic always uses the local route.
The public route table adds: 0.0.0.0/0 → igw-xxx. Any traffic destined for an IP outside the VPC CIDR follows this route to the Internet Gateway.
Private Route Table Entries
Same local route (10.0.0.0/16 → local) for intra-VPC traffic. For outbound internet access: 0.0.0.0/0 → nat-xxx. Without this route, private instances have absolutely no path to the internet.
A subnet with no 0.0.0.0/0 route at all is called an isolated subnet. This is the right choice for database tiers that should never initiate or receive external connections.
Route Tables Define Visibility, Not Subnet Names
AWS has no concept of a "public" or "private" subnet type in the API. Every subnet is created identically. The classification is entirely determined by the route table association: a subnet with 0.0.0.0/0 → igw is public, a subnet with 0.0.0.0/0 → nat is private with outbound internet, and a subnet with no 0.0.0.0/0 route is isolated. You can switch a subnet from private to public simply by updating its route table association, no re-provisioning required.
4. Security Groups vs. Network ACLs
Security Groups
A Security Group is a virtual stateful firewall applied at the Elastic Network Interface (ENI) level. It filters traffic for a specific instance or service, not an entire subnet. Every EC2 instance, RDS database, Lambda in a VPC, and ECS task has at least one security group attached.
Stateful means the group tracks connection state. If you allow inbound traffic on port 443, the return traffic for that established connection is automatically permitted outbound, even with no explicit outbound rule. You only need to describe one direction per connection.
Rules are additive: if any rule matches and allows the traffic, it passes. There are no deny rules. The default behavior for a new security group is deny-all inbound and allow-all outbound.
A powerful feature: rules can reference another security group by ID instead of a CIDR block. For example, allow inbound port 5432 from sg-alb means "any instance carrying the ALB security group," which remains correct as instances scale in and out without touching any IP ranges.
At a Glance
- Scope: instance / ENI
- Stateful: yes, return traffic auto-allowed
- Rule types: allow only, no deny
- Default inbound: deny all
- Default outbound: allow all
- SG references: yes, reference other SGs by ID
Network ACLs (NACLs)
A Network ACL (NACL) is a stateless firewall applied at the subnet boundary. Every packet entering or leaving a subnet is evaluated against the NACL, regardless of which specific instance it is going to. NACLs are an optional second layer of defense on top of security groups.
Stateless means the NACL has no memory of connections. If you allow inbound TCP port 443, the return traffic arrives on an ephemeral port (1024-65535) and must be explicitly allowed outbound. Forgetting the ephemeral port outbound rule is the most common NACL misconfiguration.
Rules are numbered and evaluated lowest-number-first. The first matching rule wins and evaluation stops; rule 100 is checked before rule 200. Use gaps between rule numbers (100, 200, 300) so you can insert new rules later without renumbering.
The default NACL (auto-created with each VPC) allows all inbound and outbound traffic. A custom NACL starts with only a * deny-all rule and blocks everything until you add explicit allow rules. Unlike security groups, NACLs support both allow and deny rules - useful for blocking a malicious IP range at the subnet level.
At a Glance
- Scope: subnet level
- Stateful: no, must allow both directions
- Ephemeral ports: allow 1024-65535 outbound for return traffic
- Rule evaluation: lowest number wins, first match stops
- Rule types: allow and deny
- Default NACL: allows all inbound + outbound
- Custom NACL: denies all (only
*deny rule)
Side-by-Side Comparison
| Feature | Security Group | NACL |
|---|---|---|
| Applied at | Instance / ENI level | Subnet level |
| Stateful? | Yes - return traffic auto allowed | No - must explicitly allow return traffic |
| Rule types | Allow only | Allow and Deny |
| Rule evaluation | All rules evaluated together | Lowest rule number wins, stops at first match |
| Typical use | Primary traffic control for resources | Broad subnet-level blocks (e.g., deny an IP range) |
# Security Groups - stateful, ENI/instance-level # Allow inbound HTTPS from the ALB security group (SG reference, not CIDR) aws ec2 authorize-security-group-ingress \ --group-id sg-app \ --protocol tcp --port 443 \ --source-group sg-alb # any instance carrying sg-alb can reach this # No return traffic rule needed - SGs are stateful # NACLs - stateless, subnet-level # MUST add both inbound AND outbound rules (stateless = no connection tracking) # Inbound rule 100: allow HTTPS from anywhere aws ec2 create-network-acl-entry \ --network-acl-id acl-xxx \ --ingress --rule-number 100 \ --protocol tcp --port-range From=443,To=443 \ --cidr-block 0.0.0.0/0 --rule-action allow # Outbound rule 100: allow ephemeral ports (return traffic) - required for stateless aws ec2 create-network-acl-entry \ --network-acl-id acl-xxx \ --egress --rule-number 100 \ --protocol tcp --port-range From=1024,To=65535 \ --cidr-block 0.0.0.0/0 --rule-action allow
5. Building a VPC via CLI
# 1. Create the VPC
VPC_ID=$(aws ec2 create-vpc --cidr-block 10.0.0.0/16 \
--tag-specifications 'ResourceType=vpc,Tags=[{Key=Name,Value=my-vpc}]' \
--query Vpc.VpcId --output text)
# 2. Create public subnets in two AZs
PUB_A=$(aws ec2 create-subnet --vpc-id $VPC_ID \
--cidr-block 10.0.1.0/24 --availability-zone us-east-1a \
--query Subnet.SubnetId --output text)
PUB_B=$(aws ec2 create-subnet --vpc-id $VPC_ID \
--cidr-block 10.0.2.0/24 --availability-zone us-east-1b \
--query Subnet.SubnetId --output text)
# 3. Create private subnets
PRIV_A=$(aws ec2 create-subnet --vpc-id $VPC_ID \
--cidr-block 10.0.10.0/24 --availability-zone us-east-1a \
--query Subnet.SubnetId --output text)
PRIV_B=$(aws ec2 create-subnet --vpc-id $VPC_ID \
--cidr-block 10.0.11.0/24 --availability-zone us-east-1b \
--query Subnet.SubnetId --output text)
# 4. Attach an Internet Gateway for public subnets
IGW=$(aws ec2 create-internet-gateway \
--query InternetGateway.InternetGatewayId --output text)
aws ec2 attach-internet-gateway --internet-gateway-id $IGW --vpc-id $VPC_ID
# 5. Create a public route table and add a route to the IGW
RT_PUB=$(aws ec2 create-route-table --vpc-id $VPC_ID \
--query RouteTable.RouteTableId --output text)
aws ec2 create-route --route-table-id $RT_PUB \
--destination-cidr-block 0.0.0.0/0 --gateway-id $IGW
# 6. Associate the public route table with both public subnets
aws ec2 associate-route-table --route-table-id $RT_PUB --subnet-id $PUB_A
aws ec2 associate-route-table --route-table-id $RT_PUB --subnet-id $PUB_B
# 7. Create a NAT Gateway in the first public subnet (requires an Elastic IP)
EIP=$(aws ec2 allocate-address --query AllocationId --output text)
NAT=$(aws ec2 create-nat-gateway \
--subnet-id $PUB_A \
--allocation-id $EIP \
--query NatGateway.NatGatewayId --output text)
aws ec2 wait nat-gateway-available --nat-gateway-ids $NAT # Takes 1-2 minutes
# 8. Create a private route table and point outbound traffic to the NAT Gateway
RT_PRIV=$(aws ec2 create-route-table --vpc-id $VPC_ID \
--query RouteTable.RouteTableId --output text)
aws ec2 create-route --route-table-id $RT_PRIV \
--destination-cidr-block 0.0.0.0/0 --nat-gateway-id $NAT
# 9. Associate the private route table with both private subnets
aws ec2 associate-route-table --route-table-id $RT_PRIV --subnet-id $PRIV_A
aws ec2 associate-route-table --route-table-id $RT_PRIV --subnet-id $PRIV_BIn practice: you will rarely build VPCs via raw CLI. The CDK (lesson 13) has an ec2.Vpc L2 construct that creates a production-ready VPC with public and private subnets, route tables, IGW, and NAT gateways across all AZs in about 10 lines of Python.
6. Observability & Advanced Features
VPC Flow Logs
Flow Logs capture metadata about IP traffic flowing through a VPC, subnet, or individual ENI. Each record includes source and destination IP, ports, protocol, byte and packet count, and whether the traffic was accepted or rejected by security groups and NACLs.
Logs are delivered to CloudWatch Logs or S3. CloudWatch is better for real-time alerting and metric filters; S3 is better for long-term storage and Athena queries.
Flow Logs do not capture packet contents, DNS query payloads, instance metadata requests, DHCP traffic, or Windows license activation traffic.
Use cases: debugging why traffic is rejected, security incident investigation, compliance auditing, detecting port scans or unusual traffic patterns.
Reachability Analyzer
Reachability Analyzer performs logical path analysis of your network topology without sending any actual traffic. You specify a source and a destination (EC2 instances, VPC endpoints, IGW, transit gateway, etc.) and the tool evaluates whether a network path exists between them.
When a path is reachable, it shows the complete hop-by-hop explanation. When it is not, it identifies exactly which component is blocking the traffic: a missing route, a security group rule, or a NACL entry.
Billed per analysis run. You do not need to wait for a traffic event to investigate a connectivity problem.
Use cases: diagnosing why an EC2 instance cannot reach another service, verifying security posture after a config change, and automating network validation in CI/CD pipelines.
VPC Traffic Mirroring
Traffic Mirroring copies inbound and outbound packets from a source ENI and forwards them to a target (another ENI, a Network Load Balancer, or a Gateway Load Balancer) for out-of-band analysis. Unlike Flow Logs, this captures the actual packet content, not just metadata.
You configure mirror sessions that define the source ENI, the target, and optional filters to limit which traffic gets mirrored. The mirrored traffic is encapsulated in VXLAN and does not affect the source workload's performance or routing.
Requires Nitro-based EC2 instances. The analysis appliance at the target typically runs an IDS/IPS tool or a network forensics platform.
Use cases: intrusion detection and prevention, deep packet inspection, network forensics, and compliance requirements that mandate full packet capture.
Ingress Routing
Ingress Routing lets you redirect traffic entering the VPC through the Internet Gateway or Virtual Private Gateway to a security appliance (firewall, IDS/IPS, or inspection device) before it reaches the destination instance. You do this by adding a route in the gateway's route table that points inbound traffic to a specific ENI in an inspection subnet.
Unlike Security Groups and NACLs, which react to traffic after it arrives at an instance boundary, Ingress Routing intercepts traffic at the VPC edge, before any instance sees it. The appliance can inspect, modify, or drop the packet before forwarding it on.
Commonly paired with AWS Gateway Load Balancer to distribute traffic across a fleet of third-party appliance instances in a transparent, highly available way.
Use cases: centralized traffic inspection across multiple workloads, regulatory compliance requiring all inbound traffic to pass through a certified security appliance, and east-west traffic control between subnets.
7. VPC Peering and Endpoints
VPC Peering
A VPC Peering connection is a direct, private network link between two VPCs, routing traffic using private IPv4 or IPv6 addresses as if they were in the same network. Peering works within the same AWS account, across different accounts, and across different regions (inter-region peering).
After creating the peering connection (a request/accept handshake), you must update the route tables in both VPCs: add a route pointing the peer's CIDR at the peering connection ID. Security groups in both VPCs must also allow the relevant traffic.
The single most important constraint: VPC peering is non-transitive. If VPC-A peers with VPC-B and VPC-B peers with VPC-C, VPC-A cannot reach VPC-C through VPC-B. Each pair that needs to communicate requires its own direct peering connection. For connecting many VPCs, Transit Gateway (covered in lesson 20) is a better fit.
Key Constraints
- No overlapping CIDRs - peered VPCs cannot share any IP range
- Non-transitive - no traffic flows through a third VPC
- No edge-to-edge routing - you cannot use one VPC's IGW or VPN to reach the peer's internet
- Route tables required - must be updated in both VPCs after peering
- One peering per pair - each pair of VPCs can have only one active peering connection
VPC Endpoints
A VPC Endpoint lets resources inside your VPC communicate with supported AWS services privately, without traffic leaving the AWS network or needing a NAT Gateway, IGW, or public IP. Traffic stays on the AWS backbone, which is both cheaper (no data-transfer charges through NAT) and more secure (no exposure to the public internet).
Gateway Endpoints
Available for S3 and DynamoDB only. A Gateway Endpoint adds a route entry to your route table pointing the service's prefix list at the endpoint. No ENI is created and there is no hourly charge.
Recommended for any private subnet that accesses S3 or DynamoDB: it removes the need for a NAT Gateway for those calls and eliminates the per-GB NAT processing cost.
Interface Endpoints (PrivateLink)
Powered by AWS PrivateLink. An Interface Endpoint creates an ENI in your subnet with a private IP, serving as the entry point for the target service. Supports most AWS services: SSM, Secrets Manager, ECR, CloudWatch, STS, KMS, and many more.
Charged per hour per AZ + per GB. For high-volume services (ECR image pulls, Secrets Manager lookups) the cost is typically offset by savings on NAT Gateway data-processing fees.
Going Deeper: Lesson 20
VPC Peering and Endpoints are just two of the ways to interconnect AWS resources. Lesson 20 covers the full spectrum of connectivity mechanisms: VPN connections (encrypted tunnels over the public internet), AWS Direct Connect (dedicated private fiber to AWS), Transit Gateway(a managed hub that replaces the n*(n-1)/2 peering mesh for large fleets of VPCs), and PrivateLink in depth. If you are designing multi-account or hybrid-cloud architectures, that lesson is the companion to this one.
Key Takeaways
- Public subnet has a route to an IGW and can host internet-facing resources (ALBs, NAT Gateways)
- Private subnet has no route to an IGW; protected resources (app servers, databases) live here
- NAT Gateway is the managed replacement for NAT Instances: no OS to patch, scales automatically, and is highly available within an AZ; use one per AZ
- Elastic IPs are static public IPv4 addresses that survive instance restarts; release unused ones or you will be charged
- Route tables are what truly make a subnet public or private:
0.0.0.0/0 → igw= public,0.0.0.0/0 → nat= private with outbound internet, no0.0.0.0/0= isolated - Security groups are stateful and the primary tool for resource-level traffic control; use SG-to-SG references instead of CIDRs inside a VPC
- NACLs are stateless subnet-level controls; always add the outbound ephemeral port rule (1024-65535) when adding an inbound allow
- Multi-AZ subnets - create subnets in at least two AZs for every tier to survive an AZ failure
- VPC Peering connects two VPCs privately but is non-transitive; for many VPCs, Transit Gateway (lesson 20) is more scalable
- VPC Endpoints let private resources reach AWS services (S3, DynamoDB, SSM, etc.) without a NAT Gateway or public internet exposure
- Flow Logs capture traffic metadata for auditing; Traffic Mirroring captures full packets for deep inspection; Reachability Analyzer diagnoses connectivity without sending traffic