Lab 16: The Borrowed NAT Gateway
Centralised egress across a peering connection. The default route is active, the peering is active, and the NAT Gateway is provably healthy because another instance is using it right now. Nothing in the application VPC can reach the internet.
- Debugging time
- ~25 min
- Reading time
- 11 min
- Reported by
- Platform Engineering
- Tier
- Professional
Application VPC has no outbound internet after moving to shared egress
Reported by Platform Engineering
We consolidated NAT. Instead of a NAT Gateway per VPC we now have one egress VPC, peered to
everything, and each VPC routes 0.0.0.0/0 at the peering connection.
The egress VPC works fine. Its own private instances reach the internet through the NAT Gateway right now — I checked before opening this.
The application VPC reaches nothing. The peering connection is active. The default route is in
the route table and the console shows it as active, not blackhole. The NAT Gateway is
available. Security groups allow all egress.
The application VPC can reach the egress VPC over the peering without any problem, so the peering is definitely carrying traffic. It just will not carry this traffic.
What you are working with
Two VPCs in one Availability Zone. No Transit Gateway.
| Resource | Configuration |
| --- | --- |
| Egress VPC | 10.150.0.0/16, internet gateway, NAT Gateway, private subnet |
| Application VPC | 10.151.0.0/16, no internet gateway, no NAT Gateway |
| Peering | active |
| App route table | 10.150.0.0/16 → pcx, 0.0.0.0/0 → pcx |
| Egress private route table | 0.0.0.0/0 → NAT Gateway, 10.151.0.0/16 → pcx |
| Control host | Egress VPC private subnet, reaches the internet through the NAT Gateway |
| App host | Application VPC, reaches nothing |
The application host also has Systems Manager interface endpoints, which is the only reason you can get a shell on it. They resolve inside the VPC, so they work regardless of the egress problem.
- EC2
App host
10.151.1.x → a public address
- RTB
App route table
0.0.0.0/0 → pcx, State: active
- GW
Peering connection
active, and carrying VPC-to-VPC traffic correctly
Dropped — Peering does not support edge-to-edge routing. It will carry traffic addressed to the peer VPC and will not carry traffic onward to that VPC’s NAT Gateway, internet gateway, VPN, or Direct Connect link.
- GW
NAT Gateway
available, healthy, in use by another instance — never reached
Every object on this path is healthy and the route reports active. The packet is discarded at the peering connection because of what peering is, not because anything is misconfigured.
Scope and constraints
- In scope: why the peering connection carries VPC-to-VPC traffic and not internet-bound traffic.
- Out of scope: the NAT Gateway, the peering connection's state, the route table's contents, and the security groups. All are healthy, and two of them are provably healthy in one command each.
- The default route really is
active. This is not a blackhole route and not a pending-acceptance peering. - The NAT Gateway really is working. Another instance is using it while you debug.
- Adding a second route, reordering, or making the route more specific does not fix this.
Deploy the broken state
cd lab-16-peering-borrowed-nat
terraform init
terraform apply
aws ssm start-session --target "$(terraform output -raw app_instance_id)"The NAT Gateway takes about ninety seconds and the interface endpoints about thirty. Session Manager will not connect to the application host until the endpoints are available.
CONTROL=$(terraform output -raw control_private_ip)Confirm the failure
From the application host:
time curl -s -m 20 -o /dev/null -w '%{http_code}\n' https://checkip.amazonaws.com
# 000
#
# real 0m20.031s000 after the full timeout. The request went out and nothing came back.
Confirm the NAT Gateway is healthy
Open a session on the control host in the egress VPC's private subnet and run the identical command:
aws ssm start-session --target "$(terraform output -raw control_instance_id)"
curl -s -m 12 https://checkip.amazonaws.com
# 3.16.85.203That works, and the address returned is the NAT Gateway's Elastic IP. Confirm it:
terraform output nat_public_ip
# 3.16.85.203So the NAT Gateway is healthy, correctly placed, and actively carrying traffic for a private subnet right now. It is not the problem, and you have proven it rather than assumed it.
Confirm the peering carries traffic
Back on the application host:
ping -c 3 "$CONTROL"
# 3 packets transmitted, 3 received, 0% packet loss
# rtt min/avg/max/mdev = 0.238/0.373/0.600/0.161 ms
exec 3<>/dev/tcp/"$CONTROL"/5000 && head -c 40 <&3
# lab-16-peering-worksSub-millisecond round trip and the listener answers. The peering connection works, in both directions, for traffic between the two VPCs.
Note that /dev/tcp is used rather than nc, because the application host has no egress and
therefore could not install anything at launch. That constraint is itself a hint about how isolated
this host is.
Read the route table and notice it is fine
aws ec2 describe-route-tables \
--route-table-ids "$(terraform output -raw app_route_table_id)" \
--query 'RouteTables[0].Routes[].[DestinationCidrBlock,VpcPeeringConnectionId,GatewayId,State,Origin]' \
--output table
# -----------------------------------------------------------------------------------
# | 10.150.0.0/16| pcx-0fbffb23dab247f2a | None | active | CreateRoute |
# | 10.151.0.0/16| None | local | active | CreateRouteTable |
# | 0.0.0.0/0 | pcx-0fbffb23dab247f2a | None | active | CreateRoute |
# -----------------------------------------------------------------------------------Three routes, all active. The default route names the same peering connection that demonstrably
carries traffic. There is nothing here to find.
Confirm the guest is not the problem either
ip route show default
# default via 10.151.1.1 dev ens5 proto dhcp src 10.151.1.168 metric 512The instance has a default route to the VPC router and is sending the packets. And DNS works, which rules out resolution:
getent hosts ssm.us-east-2.amazonaws.com
# 10.151.1.187 ssm.us-east-2.amazonaws.comResolving to an in-VPC address, because that is the interface endpoint. Everything the host can control is correct.
This debrief is part of Labs Pro
The root-cause analysis, packet-flow walkthrough, and Terraform remediation diff for this lab are available to Labs Pro members. One payment of $49, no subscription, and it covers every Pro lab now and later.
The brief, the reproduction steps, and the Terraform stay free — you can still solve this one yourself.
Already bought it? Sign in and it unlocks.