AWS VPC
Virtual Private Cloud — network design, routing, security, connectivity, and observability
Region: us-east-1 ┌─────────────────────────────────────────────────────────────────────┐ │ VPC 10.0.0.0/16 │ │ │ │ AZ: us-east-1a AZ: us-east-1b │ │ ┌──────────────────────┐ ┌──────────────────────┐ │ │ │ Public Subnet │ │ Public Subnet │ │ │ │ 10.0.0.0/24 │ │ 10.0.1.0/24 │ │ │ │ EC2 (public IP) │ │ ALB │ │ │ │ NAT Gateway ────────┼──────────┼──► IGW ──► Internet │ │ │ └──────────────────────┘ └──────────────────────┘ │ │ ┌──────────────────────┐ ┌──────────────────────┐ │ │ │ Private Subnet │ │ Private Subnet │ │ │ │ 10.0.2.0/24 │ │ 10.0.3.0/24 │ │ │ │ EC2 → NAT GW │ │ RDS (Multi-AZ) │ │ │ └──────────────────────┘ └──────────────────────┘ │ │ │ │ Route Tables · Security Groups · NACLs · Flow Logs │ │ VPC Peering / Transit Gateway / VPC Endpoints │ └─────────────────────────────────────────────────────────────────────┘
VPC Fundamentals Core

A VPC is a logically isolated virtual network within an AWS region. You define the IP address space (CIDR), subdivide it into subnets, and control traffic with route tables and security constructs.

Key concepts

ConceptDetail
CIDR blockPrimary IPv4 CIDR: /16 to /28. Up to 5 secondary CIDRs can be added. IPv6 /56 can be assigned (Amazon-provided or BYOIP).
Default VPCCreated automatically per region. Has a /16 CIDR (172.31.0.0/16), public subnets in each AZ, an IGW, and a default route table. Safe to use for testing; recreate with aws ec2 create-default-vpc if deleted.
Tenancydefault — instances share hardware with other customers. dedicated — all instances run on single-tenant hardware (significant cost premium).
DNS hostnamesOff by default on custom VPCs. Enable enableDnsHostnames to get public DNS names for instances with public IPs.
DNS resolutionenableDnsSupport must be on (default) for the Route 53 Resolver at 169.254.169.253 (or VPC base + 2) to work.

Create a VPC (AWS CLI)

Shell
# Create VPC
aws ec2 create-vpc \
  --cidr-block 10.0.0.0/16 \
  --tag-specifications 'ResourceType=vpc,Tags=[{Key=Name,Value=my-vpc}]'

# Enable DNS hostnames (required for public DNS names)
aws ec2 modify-vpc-attribute \
  --vpc-id vpc-0abc123 \
  --enable-dns-hostnames

# Add a secondary CIDR
aws ec2 associate-vpc-cidr-block \
  --vpc-id vpc-0abc123 \
  --cidr-block 10.1.0.0/16

# List VPCs
aws ec2 describe-vpcs --query 'Vpcs[*].{ID:VpcId,CIDR:CidrBlock,Default:IsDefault}'

CIDR sizing guide

PrefixAddressesUsable hostsTypical use
/1665,53665,531VPC (max range)
/204,0964,091Large subnet
/24256251Standard subnet
/273227Small subnet
/281611Minimum VPC/subnet size
AWS reserves 5 addresses in every subnet: network address, VPC router (.1), DNS (.2), future use (.3), and broadcast (.255). A /24 gives 251 usable, not 256.
Subnets Core

Subnets are subdivisions of a VPC CIDR, each tied to a single Availability Zone. A subnet's "public" or "private" character comes entirely from its route table — whether it has a route to an Internet Gateway.

Public vs private subnet

TypeRoute table hasTypical resources
Public0.0.0.0/0 → IGWLoad balancers, NAT Gateways, bastion hosts
Private0.0.0.0/0 → NAT Gateway (or none)App servers, databases, Lambda in VPC
IsolatedNo default route at allDatabases that should never reach internet

Create subnets

Shell
# Public subnet in AZ a
aws ec2 create-subnet \
  --vpc-id vpc-0abc123 \
  --cidr-block 10.0.0.0/24 \
  --availability-zone us-east-1a \
  --tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=public-1a}]'

# Enable auto-assign public IP for public subnets
aws ec2 modify-subnet-attribute \
  --subnet-id subnet-0abc123 \
  --map-public-ip-on-launch

# Private subnet in AZ a
aws ec2 create-subnet \
  --vpc-id vpc-0abc123 \
  --cidr-block 10.0.2.0/24 \
  --availability-zone us-east-1a \
  --tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=private-1a}]'

# List subnets with AZ and available IPs
aws ec2 describe-subnets \
  --filters Name=vpc-id,Values=vpc-0abc123 \
  --query 'Subnets[*].{ID:SubnetId,AZ:AvailabilityZone,CIDR:CidrBlock,Available:AvailableIpAddressCount}'

Recommended multi-tier layout

Layout
VPC: 10.0.0.0/16

Public subnets (one per AZ — for load balancers, NAT GW):
  10.0.0.0/24   us-east-1a
  10.0.1.0/24   us-east-1b
  10.0.2.0/24   us-east-1c

Private subnets (app tier):
  10.0.10.0/24  us-east-1a
  10.0.11.0/24  us-east-1b
  10.0.12.0/24  us-east-1c

Isolated subnets (data tier):
  10.0.20.0/24  us-east-1a
  10.0.21.0/24  us-east-1b
  10.0.22.0/24  us-east-1c
Route Tables Routing

Every VPC has a main route table. Subnets not explicitly associated with a custom route table use the main one. Routes are evaluated longest-prefix-first; the local route (VPC CIDR) always wins for intra-VPC traffic and cannot be deleted.

Common routes

DestinationTargetPurpose
10.0.0.0/16localIntra-VPC traffic (auto-added, non-deletable)
0.0.0.0/0igw-xxxPublic internet (public subnets)
0.0.0.0/0nat-xxxOutbound internet via NAT (private subnets)
10.1.0.0/16pcx-xxxVPC peering connection
0.0.0.0/0tgw-xxxTransit Gateway (hub-and-spoke)
pl-xxx (prefix list)vpce-xxxGateway endpoint (S3, DynamoDB)

Manage route tables

Shell
# Create a route table
aws ec2 create-route-table --vpc-id vpc-0abc123 \
  --tag-specifications 'ResourceType=route-table,Tags=[{Key=Name,Value=public-rt}]'

# Add a route to IGW
aws ec2 create-route \
  --route-table-id rtb-0abc123 \
  --destination-cidr-block 0.0.0.0/0 \
  --gateway-id igw-0abc123

# Associate subnet with route table
aws ec2 associate-route-table \
  --route-table-id rtb-0abc123 \
  --subnet-id subnet-0abc123

# View routes
aws ec2 describe-route-tables \
  --route-table-ids rtb-0abc123 \
  --query 'RouteTables[*].Routes'
Internet Gateway Routing

An Internet Gateway (IGW) enables bidirectional internet connectivity for instances with public IPs. It performs NAT for IPv4 (translating public EIP/auto-assigned IPs to private IPs) and is horizontally scaled and redundant — no bandwidth bottleneck.

How it works

Flow
Outbound: EC2 (10.0.0.5) → IGW → Internet
  IGW translates source 10.0.0.5 → public IP (EIP or auto-assigned)

Inbound: Internet → public IP → IGW → EC2 (10.0.0.5)
  IGW translates destination public IP → 10.0.0.5

Requirements:
  1. VPC has an IGW attached
  2. Subnet route table has 0.0.0.0/0 → igw-xxx
  3. Instance has a public IP (EIP or auto-assign) or is behind a public ELB
  4. Security group allows the traffic

Create and attach IGW

Shell
# Create
aws ec2 create-internet-gateway \
  --tag-specifications 'ResourceType=internet-gateway,Tags=[{Key=Name,Value=my-igw}]'

# Attach to VPC (one IGW per VPC)
aws ec2 attach-internet-gateway \
  --internet-gateway-id igw-0abc123 \
  --vpc-id vpc-0abc123

# Detach
aws ec2 detach-internet-gateway \
  --internet-gateway-id igw-0abc123 \
  --vpc-id vpc-0abc123
Egress-only IGW — For IPv6-only outbound internet access from private subnets. IPv6 addresses are globally routable, so there is no NAT; the egress-only IGW blocks unsolicited inbound connections.
NAT Gateway Routing

A NAT Gateway allows instances in private subnets to initiate outbound internet connections (e.g., to download packages) without being reachable from the internet. It is managed, scales automatically, and is highly available within a single AZ.

NAT Gateway vs NAT Instance

NAT GatewayNAT Instance
ManagementFully managed by AWSYou manage the EC2 instance
BandwidthUp to 100 Gbps (scales automatically)Limited by instance type
High availabilityHA within one AZ (deploy one per AZ)Manual — requires scripts/ASG
CostHourly + per-GB data chargeEC2 instance cost only
Security groupsNot supportedSupported
BastionCannot be used as bastionCan double as bastion

Create NAT Gateway and route private subnet

Shell
# Allocate an Elastic IP
aws ec2 allocate-address --domain vpc

# Create NAT Gateway in the public subnet
aws ec2 create-nat-gateway \
  --subnet-id subnet-public-1a \
  --allocation-id eipalloc-0abc123 \
  --tag-specifications 'ResourceType=natgateway,Tags=[{Key=Name,Value=nat-1a}]'

# Wait for it to become available
aws ec2 wait nat-gateway-available --nat-gateway-ids nat-0abc123

# Add default route in private route table pointing to NAT GW
aws ec2 create-route \
  --route-table-id rtb-private-1a \
  --destination-cidr-block 0.0.0.0/0 \
  --nat-gateway-id nat-0abc123
One NAT Gateway per AZ. A NAT Gateway is deployed in a single AZ. If that AZ fails, private subnets in other AZs routed to it lose internet access. Deploy a NAT Gateway in each AZ and route each AZ's private subnets to the local one.

Private NAT Gateway

A private NAT Gateway (no EIP) allows instances to communicate with other VPCs or on-premises networks using non-overlapping CIDRs, without internet access. Useful when you need to translate overlapping IP ranges between peered VPCs.
Security Groups Security

Security groups are stateful virtual firewalls attached to ENIs (network interfaces). Return traffic is automatically allowed. All inbound is denied by default; all outbound is allowed by default.

Key behaviours

PropertyDetail
StatefulIf inbound is allowed, the return traffic is automatically permitted — no outbound rule needed for responses.
Allow-onlyRules can only allow traffic; there is no explicit deny. To block traffic, simply have no matching allow rule.
Multiple SGsUp to 5 SGs per ENI. Rules from all attached SGs are evaluated together (union of allows).
SG referencesSource/destination can be another SG ID, not just a CIDR. This dynamically includes all instances using that SG.
Default SGAllows all inbound from itself (same SG) and all outbound. Never use the default SG for production workloads.

Manage security groups

Shell
# Create
aws ec2 create-security-group \
  --group-name web-sg \
  --description "Web tier" \
  --vpc-id vpc-0abc123

# Allow HTTPS from anywhere
aws ec2 authorize-security-group-ingress \
  --group-id sg-0abc123 \
  --protocol tcp --port 443 --cidr 0.0.0.0/0

# Allow app traffic only from the web SG (SG reference)
aws ec2 authorize-security-group-ingress \
  --group-id sg-app123 \
  --protocol tcp --port 8080 \
  --source-group sg-0abc123

# Revoke a rule
aws ec2 revoke-security-group-ingress \
  --group-id sg-0abc123 \
  --protocol tcp --port 22 --cidr 0.0.0.0/0

# Show rules
aws ec2 describe-security-group-rules \
  --filters Name=group-id,Values=sg-0abc123

Recommended layered pattern

sg-alb

Inbound: 443 from 0.0.0.0/0
Outbound: 8080 → sg-app

sg-app

Inbound: 8080 from sg-alb
Outbound: 5432 → sg-db

sg-db

Inbound: 5432 from sg-app
Outbound: (none needed)

sg-bastion

Inbound: 22 from your-ip/32
Outbound: 22 → sg-app

Network ACLs Security

Network ACLs (NACLs) are stateless firewalls applied at the subnet boundary. Both inbound and outbound rules must explicitly allow traffic (including return traffic). Rules are evaluated in ascending rule-number order; the first match wins.

Security Groups vs NACLs

Security GroupNACL
StateStateful — return traffic auto-allowedStateless — must allow both directions
Applied atENI (instance level)Subnet boundary
RulesAllow onlyAllow and Deny
EvaluationAll rules evaluated togetherLowest rule number first, first match wins
DefaultDeny all inbound, allow all outboundDefault NACL allows all; custom NACLs deny all until rules added

NACL rule numbering convention

Convention
100   Allow HTTPS inbound (443)
110   Allow HTTP inbound (80)
120   Allow ephemeral ports inbound (1024-65535) for return traffic
...
32766 *DENY all  (implicit, cannot be removed)

Manage NACLs

Shell
# Create NACL
aws ec2 create-network-acl --vpc-id vpc-0abc123

# Add inbound allow rule (rule 100, TCP 443)
aws ec2 create-network-acl-entry \
  --network-acl-id acl-0abc123 \
  --rule-number 100 \
  --protocol tcp \
  --port-range From=443,To=443 \
  --cidr-block 0.0.0.0/0 \
  --rule-action allow \
  --ingress

# Allow ephemeral return ports inbound (stateless — required!)
aws ec2 create-network-acl-entry \
  --network-acl-id acl-0abc123 \
  --rule-number 120 \
  --protocol tcp \
  --port-range From=1024,To=65535 \
  --cidr-block 0.0.0.0/0 \
  --rule-action allow \
  --ingress

# Associate NACL with subnet
aws ec2 replace-network-acl-association \
  --association-id aclassoc-0abc123 \
  --network-acl-id acl-0abc123
Remember ephemeral ports. Because NACLs are stateless, return traffic uses ephemeral ports (1024–65535 on Linux, 49152–65535 on Windows). You must add an inbound rule allowing these ports or responses will be dropped.
VPC Peering Connectivity

VPC peering creates a private network connection between two VPCs — same or different accounts, same or different regions. Traffic stays on the AWS backbone. There are no bandwidth bottlenecks or single points of failure.

Constraints

ConstraintDetail
No transitive routingIf VPC-A peers with VPC-B and VPC-B peers with VPC-C, VPC-A cannot reach VPC-C via VPC-B. Each pair needs its own peering connection.
No overlapping CIDRsPeered VPCs cannot have overlapping CIDR blocks.
Route table update requiredPeering alone doesn't route traffic — you must add routes in both VPCs pointing the remote CIDR at the peering connection.
DNS resolutionEnable "Allow DNS resolution from remote VPC" on both sides to resolve private DNS across the peering.

Set up VPC peering

Shell
# Step 1: Create peering request (from VPC A)
aws ec2 create-vpc-peering-connection \
  --vpc-id vpc-aaa \
  --peer-vpc-id vpc-bbb \
  --peer-region us-west-2  # omit for same-region

# Step 2: Accept (from VPC B's account/region)
aws ec2 accept-vpc-peering-connection \
  --vpc-peering-connection-id pcx-0abc123

# Step 3: Add routes in VPC A's route table
aws ec2 create-route \
  --route-table-id rtb-aaa \
  --destination-cidr-block 10.1.0.0/16 \
  --vpc-peering-connection-id pcx-0abc123

# Step 4: Add routes in VPC B's route table
aws ec2 create-route \
  --route-table-id rtb-bbb \
  --destination-cidr-block 10.0.0.0/16 \
  --vpc-peering-connection-id pcx-0abc123

# Step 5: Update security groups to allow traffic from peer CIDR
For more than ~10 VPCs, peering becomes a full mesh with n(n-1)/2 connections. Prefer Transit Gateway at scale.
Transit Gateway Connectivity

Transit Gateway (TGW) is a regional network hub that connects VPCs, VPNs, and Direct Connect in a hub-and-spoke model. Transitive routing is supported — any attachment can reach any other via the TGW's route tables.

Architecture

Diagram
VPC-A ──┐
VPC-B ──┤
VPC-C ──┼──► Transit Gateway ──► VPN (on-premises)
VPC-D ──┤                    └──► Direct Connect
VPC-E ──┘

Each VPC attaches via a TGW VPC attachment (one subnet per AZ).
TGW route tables control which attachments can talk to each other.

TGW route table isolation example

Design
TGW Route Table: "shared-services-rt"
  Associates: VPC-shared (DNS, AD, monitoring)
  Propagates from: all VPCs
  → All VPCs can reach shared services

TGW Route Table: "spoke-rt"
  Associates: VPC-A, VPC-B, VPC-C (app VPCs)
  Propagates from: VPC-shared only
  → App VPCs can reach shared services but NOT each other

Create and attach TGW

Shell
# Create TGW
aws ec2 create-transit-gateway \
  --description "Main TGW" \
  --options DefaultRouteTableAssociation=enable,DefaultRouteTablePropagation=enable

# Attach a VPC (one subnet per AZ recommended)
aws ec2 create-transit-gateway-vpc-attachment \
  --transit-gateway-id tgw-0abc123 \
  --vpc-id vpc-0abc123 \
  --subnet-ids subnet-1a subnet-1b

# Route VPC traffic to TGW
aws ec2 create-route \
  --route-table-id rtb-0abc123 \
  --destination-cidr-block 10.0.0.0/8 \
  --transit-gateway-id tgw-0abc123

# Share TGW to other accounts via RAM
aws ram create-resource-share \
  --name tgw-share \
  --resource-arns arn:aws:ec2:us-east-1:123456789:transit-gateway/tgw-0abc123 \
  --principals 987654321098
VPC Endpoints Connectivity

VPC Endpoints allow private connectivity to AWS services without traversing the internet, NAT, or an IGW. Traffic stays entirely within the AWS network.

Endpoint types

TypeServicesMechanismCost
Gateway endpointS3, DynamoDB onlyRoute table entry pointing a managed prefix list at the endpoint. No ENI created.Free
Interface endpointMost AWS services (EC2 API, STS, SQS, SNS, …)Creates an ENI with a private IP in your subnet. DNS resolves service hostname to that IP.Hourly + per-GB
Gateway Load Balancer endpointThird-party appliances (firewalls, IDS)Routes traffic through a GWLB for inspection before forwarding.Hourly + per-GB

Gateway endpoint for S3

Shell
# Create gateway endpoint (free, add to route tables)
aws ec2 create-vpc-endpoint \
  --vpc-id vpc-0abc123 \
  --service-name com.amazonaws.us-east-1.s3 \
  --vpc-endpoint-type Gateway \
  --route-table-ids rtb-private-1a rtb-private-1b

# The route table gets a new entry automatically:
# Destination: pl-xxxxxx (S3 prefix list) → vpce-xxxxxx

# Restrict S3 bucket access to VPC only (bucket policy)
# Add this condition to your S3 bucket policy:
"Condition": {
  "StringNotEquals": {
    "aws:SourceVpce": "vpce-0abc123"
  }
}

Interface endpoint

Shell
# Create interface endpoint for SSM (enables Session Manager without internet)
for svc in ssm ssmmessages ec2messages; do
  aws ec2 create-vpc-endpoint \
    --vpc-id vpc-0abc123 \
    --service-name com.amazonaws.us-east-1.$svc \
    --vpc-endpoint-type Interface \
    --subnet-ids subnet-private-1a subnet-private-1b \
    --security-group-ids sg-endpoints \
    --private-dns-enabled
done

# With --private-dns-enabled, the service's public DNS name
# (e.g. ssm.us-east-1.amazonaws.com) resolves to the ENI private IP
# — no code changes needed in the application.
Flow Logs Observability

VPC Flow Logs capture metadata about IP traffic flowing through VPCs, subnets, or individual ENIs. They are the primary tool for network troubleshooting, security auditing, and anomaly detection in AWS.

Flow log fields (default format)

Fields
version account-id interface-id srcaddr dstaddr srcport dstport protocol packets bytes start end action log-status

Example accepted record:
2 123456789 eni-0abc123 10.0.1.5 10.0.2.8 54321 443 6 10 4096 1620000000 1620000060 ACCEPT OK

Example rejected record:
2 123456789 eni-0abc123 1.2.3.4 10.0.1.5 54321 22 6 1 40 1620000000 1620000060 REJECT OK

Create flow logs

Shell
# Flow logs to CloudWatch Logs
aws ec2 create-flow-logs \
  --resource-type VPC \
  --resource-ids vpc-0abc123 \
  --traffic-type ALL \
  --log-destination-type cloud-watch-logs \
  --log-group-name /aws/vpc/flowlogs \
  --deliver-logs-permission-arn arn:aws:iam::123456789:role/flowlogs-role

# Flow logs to S3 (cheaper for long-term retention)
aws ec2 create-flow-logs \
  --resource-type VPC \
  --resource-ids vpc-0abc123 \
  --traffic-type ALL \
  --log-destination-type s3 \
  --log-destination arn:aws:s3:::my-flowlogs-bucket/vpc-logs/

# Custom format — include more fields
aws ec2 create-flow-logs \
  --resource-type VPC \
  --resource-ids vpc-0abc123 \
  --traffic-type ALL \
  --log-format '${version} ${account-id} ${interface-id} ${srcaddr} ${dstaddr} ${srcport} ${dstport} ${protocol} ${action} ${tcp-flags} ${type} ${pkt-srcaddr} ${pkt-dstaddr}'

Query flow logs with CloudWatch Insights

CloudWatch Insights
# Top talkers by bytes
fields srcaddr, dstaddr, bytes
| stats sum(bytes) as total by srcaddr, dstaddr
| sort total desc
| limit 20

# Find rejected traffic (security group blocks)
fields srcaddr, dstaddr, dstport, action
| filter action = "REJECT"
| stats count() as rejects by srcaddr, dstport
| sort rejects desc

# Traffic to a specific port
fields srcaddr, dstaddr, dstport, action
| filter dstport = 22 and action = "REJECT"
DNS & Route 53 Resolver DNS

Every VPC has a built-in DNS resolver at the VPC base address + 2 (e.g., 10.0.0.2 for a 10.0.0.0/16 VPC). Route 53 Resolver extends this with inbound/outbound endpoints for hybrid DNS resolution with on-premises networks.

DNS resolution in a VPC

Query typeResolution
AWS service hostnames (e.g. s3.amazonaws.com)Resolved by Route 53 Resolver to public or private IPs depending on VPC endpoint configuration.
Private hosted zonesResolved by Route 53 Resolver if the zone is associated with the VPC.
EC2 internal hostnamesip-10-0-1-5.us-east-1.compute.internal — resolved within the VPC automatically.
External hostnamesForwarded by the Resolver to public DNS (Route 53 recursive resolver).

Route 53 Resolver endpoints (hybrid DNS)

Flow
Inbound endpoint  — on-premises DNS → Resolver endpoint ENIs in VPC
                    on-prem servers can query AWS private hosted zones

Outbound endpoint — Route 53 Resolver → Resolver endpoint → on-premises DNS
                    VPC instances can resolve on-premises domain names
                    using Resolver forwarding rules
Shell
# Create outbound endpoint (for forwarding to on-premises)
aws route53resolver create-resolver-endpoint \
  --creator-request-id my-endpoint \
  --name outbound-to-onprem \
  --security-group-ids sg-0abc123 \
  --direction OUTBOUND \
  --ip-addresses SubnetId=subnet-1a SubnetId=subnet-1b

# Create forwarding rule: corp.example.com → on-premises DNS
aws route53resolver create-resolver-rule \
  --creator-request-id my-rule \
  --rule-type FORWARD \
  --domain-name corp.example.com \
  --resolver-endpoint-id rslvr-out-0abc123 \
  --target-ips Ip=192.168.1.53,Port=53

# Associate rule with VPC
aws route53resolver associate-resolver-rule \
  --resolver-rule-id rslvr-rr-0abc123 \
  --vpc-id vpc-0abc123

DHCP option sets

Shell
# Create custom DHCP options (e.g., custom domain search)
aws ec2 create-dhcp-options \
  --dhcp-configurations \
    "Key=domain-name,Values=corp.example.com" \
    "Key=domain-name-servers,Values=AmazonProvidedDNS"

# Associate with VPC
aws ec2 associate-dhcp-options \
  --dhcp-options-id dopt-0abc123 \
  --vpc-id vpc-0abc123
Cheat Sheet Reference

Common AWS CLI one-liners

Shell
# List all VPCs
aws ec2 describe-vpcs --query 'Vpcs[*].{ID:VpcId,CIDR:CidrBlock,Name:Tags[?Key==`Name`]|[0].Value}'

# Find which SG is blocking a port
aws ec2 describe-security-group-rules \
  --filters Name=group-id,Values=sg-0abc123 \
  --query 'SecurityGroupRules[?ToPort==`443`]'

# Find instances in a subnet
aws ec2 describe-instances \
  --filters Name=subnet-id,Values=subnet-0abc123 \
  --query 'Reservations[*].Instances[*].{ID:InstanceId,IP:PrivateIpAddress,State:State.Name}'

# Check NAT Gateway data usage
aws cloudwatch get-metric-statistics \
  --namespace AWS/NATGateway \
  --metric-name BytesOutToDestination \
  --dimensions Name=NatGatewayId,Value=nat-0abc123 \
  --start-time 2026-05-12T00:00:00Z \
  --end-time 2026-05-13T00:00:00Z \
  --period 86400 --statistics Sum

# Trace route table for a subnet
aws ec2 describe-route-tables \
  --filters Name=association.subnet-id,Values=subnet-0abc123 \
  --query 'RouteTables[*].Routes'

VPC / Subnets

VPC CIDR: /16 to /28
AWS reserves 5 IPs per subnet
Public = route to IGW
Default VPC: 172.31.0.0/16

Security Groups

Stateful — no return rules needed
Allow-only (no deny rules)
Reference SG IDs, not just CIDRs
Max 5 SGs per ENI

NACLs

Stateless — allow both directions
First matching rule number wins
Always allow ephemeral 1024–65535
Custom NACL starts with DENY ALL

NAT Gateway

Deploy one per AZ
Lives in public subnet
Requires an EIP
No security groups; use route tables

VPC Peering

No transitive routing
No overlapping CIDRs
Update route tables on both sides
n(n-1)/2 connections at full mesh

VPC Endpoints

Gateway: S3 + DynamoDB (free)
Interface: most services (hourly fee)
--private-dns-enabled for transparent use
Restrict via endpoint policies

Flow Logs

VPC / Subnet / ENI level
ACCEPT or REJECT per flow
S3 cheaper; CWL for live queries
Does not capture DNS or DHCP

Transit Gateway

Hub-and-spoke replaces full mesh
Supports transitive routing
Share via RAM cross-account
Inter-region peering available