Private subnet without NAT — no outbound internet
Instances in private subnets cannot reach package repos or APIs
Focused incident mode — strips away everything but this runbook.
Symptoms
- Instances in private subnets cannot reach package repos or APIs
- SSM/AWS APIs unreachable from private instances without VPC endpoints
- curl to https://aws.amazon.com hangs while VPC-internal traffic works
Evidence to grab
Collect these artifacts before you change anything — they prove (or rule out) each cause.
Routes for the private subnet
aws ec2 describe-route-tables --filters Name=association.subnet-id,Values=<SUBNET-ID> --query 'RouteTables[0].Routes'NAT gateway inventory
aws ec2 describe-nat-gateways --filter Name=state,Values=available --query 'NatGateways[].{Id:NatGatewayId,Subnet:SubnetId,State:State}'
Diagnostic flow
Run the steps in order, marking each claim confirmed or refuted. The evidence you gather feeds the ranked causes below.
Guided decision tree is ready.
Steps with commands, expected output, and pass/fail branches.
Ranked root causes
Evidence checklist
- describe-nat-gateways returns empty (or only failed/deleting)
- No default route anywhere
Confirm before fixing
aws ec2 describe-nat-gateways --filter Name=vpc-id,Values=<VPC-ID> --query 'NatGateways[].{Id:NatGatewayId,State:State,Subnet:SubnetId}'Are there any NAT gateways in this VPC?
Fix
aws ec2 allocate-address --domain vpc && aws ec2 create-nat-gateway --subnet-id <PUBLIC-SUBNET-ID> --allocation-id <ALLOCATION-ID>Create a NAT GW in a public subnet (requires an EIP)
Expected: nat-0...
Resolution runbook
Apply from top to bottom after the cause is confirmed. Review every command before running it — risk tags are shown on each.
aws ec2 describe-nat-gateways --filter Name=vpc-id,Values=<VPC-ID> --query 'NatGateways[].{Id:NatGatewayId,State:State}'Confirm the NAT inventory
aws ec2 allocate-address --domain vpcAllocate an EIP for the NAT if none exists
aws ec2 create-nat-gateway --subnet-id <PUBLIC-SUBNET-ID> --allocation-id <ALLOCATION-ID>Create the NAT gateway (wait for available)
Expected: nat-...
aws ec2 create-route --route-table-id <ROUTE-TABLE-ID> --destination-cidr-block 0.0.0.0/0 --nat-gateway-id <NAT-GATEWAY-ID>Route private traffic through it
aws ssm send-command --instance-ids <INSTANCE-ID> --document-name AWS-RunShellScript --parameters 'commands=["curl -sI https://aws.amazon.com/ | head -1"]'Verify egress restored
Expected: HTTP/2 200 (or similar)
Prevention
- Provision NAT gateways per-AZ in infrastructure code with route table exports.
- Consider VPC endpoints for AWS APIs to avoid NAT altogether.
- Add a drift check: every private route table must expose a default route.
Related runbooks
Same stack, same failure family — worth a skim.
ping/curl to the internet fails from an instance in a public subnet
run-instances fails: There aren't enough IP addresses available in subnet
Traffic fails in one direction only (e.g. inbound works, replies dropped)
Commands marked READ CHANGE DESTRUCTIVE — never run DESTRUCTIVE commands against production data you cannot restore. Copy → verify → execute.