AWS Networking

The AWS VPC Connectivity Troubleshooting Playbook: Why Can't A Reach B?

Quick take: When “A can’t reach B” in a VPC, do not stare at the security group — walk the packet in order and stop at the first layer that could drop it. A packet from A crosses, in this exact sequence: the route table (is there a route to B at all?), the subnet’s stateless NACL, the ENI’s stateful security group, and finally lands on a target that must actually be listening; if B lives in another VPC or on-prem it also crosses a Transit Gateway, a peering connection, or a VPN/DX, and every hop is filtered again by DNS and MTU. Almost every AWS-network drop is silent — the client just times out — so a timeout tells you nothing about which layer failed. The whole skill is (1) knowing the ordered path, (2) reading the two signals that aren’t silent (RST = “reached the host, app refused”; ICMP unreachable = “no route”), and (3) getting a deterministic answer from Reachability Analyzer and VPC Flow Logs instead of guessing.

A platform team pages you at 2 a.m.: the new order-service instance in ap-south-1 cannot reach the payments API in a peered VPC. The security group “looks right.” The instance is healthy. curl from a bastion in the same subnet works; curl from the order service hangs for 30 seconds and dies with Connection timed out. Three engineers open three consoles and all three open the security group — the single most over-inspected object in AWS networking — and find nothing wrong, because nothing is wrong there. The packet never got far enough to be judged by a security group: the peered VPC’s route table had a route back to the wrong CIDR after a subnet was re-CIDR’d, so the reply was black-holed one layer earlier. Two hours gone on the wrong layer.

That incident is the whole reason this playbook exists. “Traffic won’t flow” is not one problem; it is a dozen different problems that all present as the same symptom — a silent timeout — at a dozen different layers. If you debug them in a random order, or worse always start at the security group, you will burn hours on healthy layers while the broken one sits three hops upstream. The senior move is mechanical: there is exactly one packet path, it has a fixed order, and you check the layers in that order, using tools that give a deterministic answer rather than a hunch. This article is that path. We define every layer a packet crosses — route table, public-IP/EIP, subnet/AZ, security group, NACL, DNS, MTU, Transit Gateway, peering, endpoint, and on-prem VPN/DX — enumerate exactly how each one fails, then hand you a master symptom→layer→confirm→fix table, a Reachability Analyzer explanation-code reference, and a fast decision tree. Every check shows both the aws CLI command and the Terraform that would have prevented it. This is the map you keep open when the pager goes off, and it maps directly to the networking domains of SAA-C03, SOA-C02, and the advanced networking specialty ANS-C01.

If you want the ground-up build first — CIDRs, subnets, route tables, gateways — read AWS VPC From Scratch: Subnets, Route Tables and IGW. This playbook assumes that foundation and goes straight to why the packet stopped.

What problem this solves

In a VPC, a single connection request is judged independently by as many as eleven different controls, evaluated in a fixed order, and any one of them can drop the packet without logging a thing on the instance. The application sees Connection timed out. That is the entire diagnostic problem: the symptom is identical no matter which layer failed, so the symptom carries almost no information. Without a systematic method you are reduced to changing one thing at a time and re-testing — the slowest possible debugging loop, and the one most likely to “fix” the wrong layer (widening a security group that was never the problem) and leave a real hole behind.

What breaks without this discipline is a recognisable pattern of wasted incidents. An engineer spends an afternoon on a security group when the subnet’s route table has no route to the destination. A team “works from AZ-a but not AZ-b” and blames the app, when the AZ-b subnet is simply associated with a route table that has no NAT route. A connection is fine for small requests and dies on large ones, and nobody suspects MTU because the concept never comes up until it bites. A Transit Gateway attachment is associated to a route table but the route was never propagated, so half the mesh is dark. Someone peers two VPCs, adds routes on one side only, and cannot understand why traffic goes out but never comes back. Each of these is a five-minute fix once you know which layer — and a lost day when you don’t.

Who hits this: everyone past a single instance in a single subnet. It bites hardest at the boundaries — where a packet leaves one blast radius and enters another: subnet→subnet across a NACL, VPC→VPC across peering or a TGW, cloud→on-prem across a VPN, and app→AWS-service across a VPC endpoint. Those boundaries are where ownership splits (the platform team owns the TGW and NACLs; the app team owns the security groups and the instance) so no single person can see the whole path, and the packet dies in the seam between two teams. The cure is a shared, ordered mental model of the path — which is exactly what a playbook is.

Here is the whole field on one page: every layer a packet crosses, in order, what it does, its classic failure, and the signal it produces.

# Layer What it enforces Classic failure Signal you see
1 Route table Is there a path to the destination CIDR? No route / wrong table associated / more-specific route / blackhole Silent timeout
2 Public IP / EIP Can this instance be reached from / reach the internet? No public IP or EIP; auto-assign off Silent timeout (internet only)
3 Subnet / AZ Is the resource where you think, in a reachable AZ? Instance in a private subnet, or an AZ with no NAT/route Silent timeout, or “one AZ works”
4 Security group Allow-only, stateful, per-ENI Missing ingress port/source; wrong SG referenced; wrong ENI Silent timeout
5 Network ACL Allow+deny, stateless, per-subnet DENY shadows ALLOW; missing outbound ephemeral range Silent timeout
6 DNS Name → IP; private zones enableDnsHostnames off; private zone not associated; endpoint private DNS off NXDOMAIN / resolves to public IP
7 MTU / fragmentation Packet size across the path Jumbo over IGW/VPN; DF bit + blocked PMTUD Small requests work, large hang
8 Transit Gateway Inter-VPC/on-prem hub routing Association without propagation; wrong TGW route table; appliance mode Silent timeout, asymmetric
9 VPC peering 1:1 non-transitive VPC link One-sided routes; non-transitivity; cross-region SG-ref; overlapping CIDR Timeout; “out but not back”
10 VPC endpoint Private path to AWS services Missing prefix-list route; restrictive policy; private DNS off; endpoint SG Timeout or AccessDenied
11 On-prem VPN / DX Hybrid to your data centre Tunnel down; BGP not advertising; asymmetric; MSS/MTU Intermittent; one-tunnel; timeout

The rest of this article is that table, expanded — one section per layer with exactly how it fails and how to confirm it — followed by the tools that turn a silent timeout into a named component.

Learning objectives

By the end of this article you can:

Prerequisites & where this fits

You should be comfortable with VPC fundamentals — CIDR blocks, subnets, route tables, internet and NAT gateways — and able to launch an EC2 instance and run the AWS CLI with credentials. You do not need to be a network specialist; the goal here is to make you the person in the room who can localise any VPC drop in minutes.

This playbook is the hub that the focused deep-dives spoke into. Each layer below has a dedicated article that goes deeper on building that component; this one is about diagnosing the whole path when it breaks.

Layer Owned by Build-it deep-dive This playbook covers
Route tables, IGW Platform / networking VPC From Scratch: Subnets, Route Tables, IGW How routing fails and how to confirm it
SG + NACL Split (app / platform) Security Groups vs NACLs Deep Dive Where they sit in the path, quick failure modes
NAT egress Platform NAT Gateway: Private-Subnet Egress “No internet from private subnet” checks
VPC peering Networking VPC Peering: Setup, Routing, Limits Non-transitivity, one-sided routes, SG-ref
Transit Gateway Networking Transit Gateway Hub-and-Spoke Association vs propagation, appliance mode
VPC endpoints Platform PrivateLink & VPC Endpoints Prefix list, policy, private DNS failures
Flow Logs Observability VPC Flow Logs for Network Troubleshooting Reading REJECTs to name the layer
VPN / DX Networking Site-to-Site VPN Hybrid Tunnel, BGP, asymmetric, MSS

Core concepts

One packet, one ordered path

The mental model is a pipeline, not a cloud. A request from source A to destination B is inspected by a fixed sequence of controls, and the packet dies at the first one that says no. If you internalise the order, diagnosis becomes “walk the list until a layer could be responsible, confirm it, move on.” Here is the canonical path for an intra-VPC TCP connection, both directions:

Hop Control Direction What it decides If it drops, you see
1 Source route table out Is there a route toward B’s IP? Silent timeout (or ICMP unreachable if no route at all)
2 Source SG egress out Is outbound to B:port allowed? Silent timeout
3 Source subnet NACL out out Is egress to B allowed by an ordered rule? Silent timeout
4 (fabric) VPC router / TGW / peering / VPN carries it Depends on layer
5 Dest subnet NACL in in Is ingress from A:port allowed? Silent timeout
6 Dest SG ingress in Is inbound from A (CIDR or SG-ref) on port allowed? Silent timeout
7 Dest ENI / instance in Is the address the ENI’s, is src/dst check ok, is anything listening? Timeout (dropped) or RST (nothing listening)
8 Dest SG (stateful) out reply Reply auto-allowed (stateful) — (automatic)
9 Dest NACL out out reply Reply to A’s ephemeral port must be allowed (stateless) Silent timeout
10 Return route table out reply Is there a route back to A? “Out but never back” — the classic asymmetric symptom

Two facts on this table do most of the work in real incidents. First, the security group is stateful (hop 8 is automatic) but the NACL is stateless (hop 9 must be allowed explicitly), which is why the single most common block is a missing outbound ephemeral-range rule on a custom NACL. Second, the return path is a path too — hop 10 — so “the request left but the reply never came” is almost always a routing asymmetry, not a firewall.

The three signals: silent, loud, and unreachable

Most AWS drops are silent, but not all. The signal narrows the layer before you run a single command.

What the client reports What it means Where the packet is First thing to check
Connection timed out (hangs ~30s) Packet dropped by a stateful firewall or routing black hole Died at route/SG/NACL, or reply lost Route → SG → NACL, in that order
Connection refused (instant RST) Packet reached the host; nothing is listening on that port, or a host firewall RST On the target OS ss -ltnp, host firewall, app health
No route to host / ICMP net-unreachable Local stack has no route, or a device returned unreachable Source routing Source route table, subnet association
Resets mid-stream after working Idle timeout, NAT/LB timeout, or MTU black hole on large payloads Stateful device or path MTU Idle timeouts, MTU/MSS
NXDOMAIN / wrong IP DNS, not connectivity Resolver enableDnsHostnames, private zones, resolver
Works small, hangs large Path MTU / fragmentation MTU mismatch on a hop MTU table, DF bit, PMTUD
Intermittent / ~50% loss One dead VPN tunnel, asymmetric ECMP, or one bad target Multi-path layer Tunnel state, appliance mode, target health

Burn this in: timeout = firewall/route (silent); RST = the host answered (not a network drop); unreachable = routing. Half of all mis-diagnoses come from treating a Connection refused as a security-group problem — it is the opposite, proof the packet crossed every firewall and reached a host that has nothing on that port.

Which layers drop silently

You cannot log your way out of a silent drop from the instance; the packet never arrives. That is precisely why Flow Logs (which record the drop at the ENI/subnet) and Reachability Analyzer (which reasons about config) exist.

Layer Drop behaviour Logged where
Route table (no route) Silent Flow Logs may show no matching record; RA explains it
Security group Silent (no reject packet) Flow Logs REJECT (ingress or egress)
Network ACL Silent Flow Logs REJECT
Host OS firewall RST (usually) or silent On the host only
No listener on port RST On the host (ss), not in Flow Logs as REJECT
MTU black hole Silent (large packets vanish) Nowhere obvious — inferred from size dependence
Blackhole route Silent Route shows state: blackhole

Layer 1 — Route tables: always the first suspect

Before any firewall runs, the VPC router must have a route toward the destination. No route means the packet is dropped at hop 1, and this is the layer engineers skip most because it feels too basic to be wrong. It is wrong constantly — after a re-CIDR, a subnet move, a Terraform apply that replaced a NAT gateway, or a TGW attachment that lost its route.

Every subnet is associated with exactly one route table (explicitly, or the VPC main table by default). The router does longest-prefix match: the most specific matching route wins, and local (the VPC CIDR) always wins within the VPC and cannot be overridden. These failure modes cover essentially every routing incident:

# Symptom Root cause Confirm Fix
1 Instance can’t reach internet No 0.0.0.0/0 → igw/nat route aws ec2 describe-route-tables --filters Name=association.subnet-id,Values=subnet-x Add default route to IGW (public) or NAT (private)
2 Works in AZ-a, times out in AZ-b AZ-b subnet associated with a table lacking the NAT/route Compare the two subnets’ associated tables Associate AZ-b subnet with the right table (or add per-AZ NAT)
3 Can’t reach peered VPC No route to the peer CIDR, or one-sided Check both VPCs’ tables for pcx-… route Add the peer CIDR route on both sides
4 Can’t reach on-prem via TGW Subnet table missing on-prem CIDR → tgw-… describe-route-tables for the on-prem CIDR Add on-prem CIDR → TGW route
5 Traffic to a service ignores the endpoint No gateway-endpoint prefix-list route Look for pl-… → vpce-… Endpoint route is auto-added to associated tables only; associate them
6 Some traffic vanishes after a change Blackhole route (target deleted) Route State = blackhole Delete/repoint the blackhole route
7 Only part of a CIDR reaches the appliance A more-specific route steals it RA MORE_SPECIFIC_ROUTE / read the table Adjust prefixes; a /24 beats a /16
8 Main-table subnets behave differently Subnet uses the main table implicitly associations with Main: true Explicitly associate a purpose-built table

The route target keyword tells you where a packet is being sent — memorise the vocabulary so a route table reads at a glance:

Target prefix Sends to Typical route Gotcha
local Within-VPC (every CIDR block) VPC CIDR → local Cannot be deleted or overridden by a more-specific route to another target within the VPC CIDR
igw- Internet gateway 0.0.0.0/0 → igw-… Needs a public IP/EIP on the ENI to actually work
nat- NAT gateway (egress only) 0.0.0.0/0 → nat-… NAT lives in a public subnet; private subnet routes to it
eigw- Egress-only IGW (IPv6) ::/0 → eigw-… IPv6-only; IGW handles IPv4
pcx- VPC peering connection peer CIDR → pcx-… Non-transitive; needed on both sides
tgw- Transit Gateway remote CIDR → tgw-… Also needs TGW route-table association and propagation
vgw- Virtual private gateway (VPN/DX) on-prem CIDR → vgw-… Or route to a TGW that owns the VPN
vpce- Gateway VPC endpoint (S3/DDB) pl-… → vpce-… Prefix-list target; auto-managed
eni- Network interface (appliance) 0.0.0.0/0 → eni-… Appliance must have src/dst check off
i- Instance (legacy NAT instance) 0.0.0.0/0 → i-… Same src/dst-check rule
nat-/carrier/lgw Wavelength / Outposts local gw edge routes Niche; know they exist

And the precedence rules that decide which route wins when several match:

Rule Behaviour Consequence
Longest-prefix match Most specific CIDR wins A /32 or /24 overrides a /0
local supremacy The VPC-CIDR local route always wins inside the VPC You cannot force in-VPC traffic to an appliance via the main table alone (need ingress routing / more-specific outside local)
Static vs propagated On a subnet table, a static route beats a propagated one for the same prefix A stale static route shadows a good BGP route
Blackhole A route whose target is gone still matches and drops Silent loss until you spot state: blackhole
One table per subnet Only the associated table applies Editing the wrong table changes nothing
Main table default Unassociated subnets use the main table Surprises when the main table is permissive or empty
# Which route table actually governs this subnet, and what does it say?
aws ec2 describe-route-tables \
  --filters "Name=association.subnet-id,Values=subnet-0abc123" \
  --query 'RouteTables[].Routes[].{Dest:DestinationCidrBlock,Target:GatewayId||NatGatewayId||TransitGatewayId||VpcPeeringConnectionId,State:State}' \
  --output table
# Terraform: the route that prevents "works in AZ-a not AZ-b" — associate EVERY
# private subnet explicitly, and give each AZ its own NAT to avoid cross-AZ data cost.
resource "aws_route_table" "private" {
  for_each = toset(["a", "b"])
  vpc_id   = aws_vpc.main.id
}
resource "aws_route" "private_default" {
  for_each               = aws_route_table.private
  route_table_id         = each.value.id
  destination_cidr_block = "0.0.0.0/0"
  nat_gateway_id         = aws_nat_gateway.per_az[each.key].id
}
resource "aws_route_table_association" "private" {
  for_each       = aws_subnet.private
  subnet_id      = each.value.id
  route_table_id = aws_route_table.private[each.value.availability_zone_suffix].id
}

Layer 2 — Public reachability: EIP, subnet, and AZ

If B is on the internet (or A is being reached from the internet), routing is necessary but not sufficient — the ENI also needs a public identity. A public subnet is defined as one whose route table points 0.0.0.0/0 at an IGW; an instance in it still cannot be reached from the internet unless it has a public IPv4 address or an Elastic IP. Auto-assign-public-IP is a subnet attribute that is off by default on custom subnets, so instances launched there are private even in a “public” subnet.

Check Command / attribute Pass criteria
Public IP present describe-instances … Association.PublicIp Non-null public IP or EIP
Auto-assign on subnet describe-subnets … MapPublicIpOnLaunch true for public subnets
Default route to IGW route table has 0.0.0.0/0 → igw-… Present and not blackhole
IGW attached to VPC describe-internet-gateways Attachments[].State = available
NACL allows in+out subnet NACL 80/443 in, ephemeral out
SG allows the port ENI SG ingress The exact port from the client CIDR
Not blocked by VPC BPA describe-vpc-block-public-access-* Not in block-bidirectional

The subnet-type vocabulary decides which of these even apply:

Subnet type Default route Public IP? Reachable from internet Reaches internet
Public 0.0.0.0/0 → igw Yes (or EIP) Yes Yes
Private (with NAT) 0.0.0.0/0 → nat No No Yes (egress via NAT)
Isolated no default route No No No
Public route, no public IP 0.0.0.0/0 → igw No No No (surprise!)

For egress-from-private-subnet failures specifically — “the instance can’t yum update” — the full path is: private route table → NAT gateway → NAT’s public subnet route → IGW. Any missing link is a silent timeout. The dedicated walk-through is NAT Gateway: Private-Subnet Egress.

# Prove the instance has (or lacks) a public identity, in one call:
aws ec2 describe-instances --instance-ids i-0abc \
  --query 'Reservations[].Instances[].{PrivIP:PrivateIpAddress,PubIP:PublicIpAddress,Subnet:SubnetId,SrcDstCheck:SourceDestCheck}' \
  --output table

Layer 3 — The two firewalls, in path order

Both firewalls are covered exhaustively in the Security Groups vs NACLs Deep Dive; here they are just two hops on the path. The one fact that matters for the packet order: the NACL is evaluated at the subnet edge (outer gate), the security group at the ENI (inner gate), and they behave oppositely on the return trip.

Dimension Security group Network ACL
Scope Per-ENI Per-subnet
State Stateful — reply auto-allowed Stateless — reply needs its own rule
Rules Allow-only Allow and deny
Evaluation Union of all rules (unordered) Ordered by rule number, first match wins, then * deny
Return traffic Automatic Must allow outbound ephemeral 1024-65535
Default (custom) Deny inbound, allow all outbound Custom NACL denies everything until you add rules
Cross-reference Can reference another SG CIDR only

Security-group failure modes, in the order you should check them:

# Symptom Cause Confirm Fix
1 Timeout to a port No ingress rule for that port/source describe-security-groups authorize-security-group-ingress port + source
2 SG “looks right,” still blocked SG not attached to this ENI describe-instances → Groups[] per ENI Attach SG to the correct interface
3 Referenced SG doesn’t work Referenced the wrong SG id / same-SG assumption Read the rule’s ReferencedGroupId Reference the source tier’s SG
4 Egress fails after a change Default allow-all egress was replaced Check egress rules Restore an egress allow for the dest/port
5 v6 clients fail, v4 works No ::/0 rule (v4 and v6 are separate) Look for IPv6 CIDR rules Add the IPv6 ingress rule
6 Cross-region peer SG-ref ignored SG referencing not supported cross-region RA UNKNOWN_PEERED_SGS Use CIDR rules across regions

NACL failure modes:

# Symptom Cause Confirm Fix
1 Inbound ACCEPT, reply lost Outbound ephemeral range missing Flow Logs pair; describe-network-acls Allow outbound tcp 1024-65535 to client CIDR
2 Rule exists but denied Lower-numbered DENY shadows it Read rules in number order Renumber the ALLOW below the DENY
3 Everything blocked on a new subnet Custom NACL default-denies Check for only the * rule Add explicit ALLOWs both directions
4 UDP/ICMP fails, TCP ok Protocol-specific rule missing Check protocol column Add the protocol’s rule + return range
5 Return from NAT/ELB dropped Wrong ephemeral range for the middlebox Know the client’s range (table below) Widen to 1024-65535

The ephemeral range depends on who is the client sending the reply-bound traffic — get this wrong and a NACL silently eats replies:

Client initiating Ephemeral source-port range Safe NACL outbound rule
Linux kernel (modern) 32768–60999 1024-65535
Windows Server 2008+ 49152–65535 1024-65535
NAT gateway 1024–65535 1024-65535
Elastic/Network Load Balancer 1024–65535 1024-65535
AWS Lambda (in-VPC ENI) 1024–65535 1024-65535

The universal safe answer is 1024-65535 outbound — anything narrower risks dropping some client’s replies.

Layer 4 — DNS: resolves to nothing, or to the wrong IP

A “connectivity” failure is often a name-resolution failure wearing a trench coat. If the name doesn’t resolve, or resolves to a public IP when you wanted the private one, no amount of route/SG/NACL work helps. VPC DNS hinges on two VPC attributes and the .2 Route 53 Resolver.

Attribute Default What it controls Break it and…
enableDnsSupport true The .2 resolver answers at all DNS times out entirely
enableDnsHostnames false on custom VPCs Instances get public DNS names; required for interface-endpoint private DNS and private hosted zones to resolve Private zone / endpoint names silently resolve to public IPs or NXDOMAIN
Resolver address VPC_base + 2 (e.g. 10.0.0.2) and 169.254.169.253 Where instances send queries Custom /etc/resolv.conf bypasses it
Private hosted zone associated to specific VPCs Which VPCs can resolve the private zone Unassociated VPC gets public/NXDOMAIN
Resolver rules / endpoints none by default Hybrid on-prem ↔ VPC resolution On-prem names unresolvable, or vice-versa

DNS failure modes:

# Symptom Cause Confirm Fix
1 Name NXDOMAIN inside VPC Private hosted zone not associated to the VPC dig name @10.0.0.2 vs Route 53 zone assoc Associate the PHZ with the VPC
2 Endpoint name resolves to public IP Interface endpoint private DNS off, or enableDnsHostnames off dig service.region.amazonaws.com returns public IP Enable private DNS on the endpoint + DNS hostnames
3 DNS times out completely enableDnsSupport off, or NACL/SG blocks UDP/TCP 53 to resolver dig @10.0.0.2 hangs Enable DNS support; allow 53 to .2
4 On-prem names fail from VPC No Route 53 outbound resolver endpoint / rule Query on-prem name fails Create resolver endpoint + forwarding rule
5 VPC names fail from on-prem No inbound resolver endpoint On-prem dig to VPC name fails Create inbound endpoint; point on-prem at it
6 Split-horizon returns wrong record Overlapping public + private zone precedence Compare public vs private answer Fix the private zone records
7 Cross-account private zone dark PHZ not shared/associated cross-account Check AssociateVPCWithHostedZone Authorise + associate the VPC
8 Stale record after failover TTL too long / client caching dig shows old IP, TTL high Lower TTL; flush client cache
# Resolve against the VPC's own resolver, not your laptop's, to prove the VPC's view:
dig +short myservice.internal @169.254.169.253
dig +short com.amazonaws.ap-south-1.s3 @169.254.169.253   # should be a private 10.x if endpoint private DNS is on

Layer 5 — MTU and fragmentation: works small, hangs large

This is the layer nobody suspects because it produces the weirdest symptom: small requests succeed, large ones hang. An SSH handshake works; scp of a big file stalls at 0%. A health check passes; a real API response times out. That fingerprint is almost always MTU — a hop in the path can’t carry the packet size, and because the DF (Don’t Fragment) bit is set (as it is on virtually all modern TCP), the oversized packet is dropped rather than fragmented, and if the ICMP “fragmentation needed” message that Path MTU Discovery relies on is blocked by a firewall, the sender never learns to shrink. The connection black-holes.

Know the MTU ceiling of every path — this table ends the argument:

Path Max MTU Jumbo (9001)? Note
Within a VPC, same region 9001 Yes Jumbo frames supported between instances
Over an internet gateway 1500 No Anything larger is dropped to the internet
VPC peering, same region 9001 Yes Jumbo supported intra-region
VPC peering, inter-region 1500 No Jumbo not supported across regions
Transit Gateway (VPC/DX/peering attach) 8500 No (caps at 8500) 9001 frames exceed it — historically silently dropped
Transit Gateway VPN attachment 1500 No VPN caps the whole path
Site-to-Site VPN 1500 No IPsec overhead → effective MSS ≈ 1379; clamp it
Direct Connect (private/transit VIF) 1500 or 9001 Yes (if configured) Jumbo must be enabled on the VIF end-to-end
Traffic through a NAT gateway 1500 to internet No Same as IGW downstream

MTU/fragmentation failure modes:

# Symptom Cause Confirm Fix
1 Small works, large hangs (any path) DF-set oversized packet dropped; PMTUD ICMP blocked ping -M do -s 1472 dest fails; -s 1400 works Lower MTU or clamp MSS to path; allow ICMP type 3 code 4
2 Jumbo instances can’t cross IGW/TGW/VPN 9001 > path max (1500/8500) ip link shows mtu 9001; large flows stall Set instance MTU to 1500 for those paths, or rely on TGW PMTUD
3 VPN bulk transfer stalls IPsec overhead + DF, no MSS clamp Works interactively, dies on bulk MSS clamp to ~1379 on the tunnel / instance
4 Intermittent large-response failures Asymmetric path with different MTUs Size-dependent, direction-dependent Normalise MTU; enable PMTUD both ways
5 ICMP blocked kills PMTUD NACL/SG/host drops ICMP frag-needed PMTUD never converges Allow ICMP type 3 code 4 inbound
# The definitive PMTUD probe from the instance (Linux): find the largest payload that survives.
ping -M do -s 1472 10.0.2.50    # 1472 + 28 = 1500; if this fails but -s 1400 works, MTU < 1500 on the path
# Clamp TCP MSS on egress so large flows never exceed the path (interface-wide):
sudo iptables -t mangle -A FORWARD -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu

Layer 6 — Beyond the VPC: TGW, peering, endpoints, and on-prem

When B lives outside the source VPC, the packet crosses one more fabric, each with its own routing plane and its own failure modes.

Transit Gateway

A TGW is a regional router with its own route tables, separate from VPC route tables. The trap is that an attachment needs two independent things: an association (which TGW route table governs traffic from this attachment) and a propagation (which attachments’ CIDRs get advertised into a TGW route table). Association without propagation = the attachment can send but its CIDR is invisible to others. Full walk-through: Transit Gateway Hub-and-Spoke.

# Symptom Cause Confirm Fix
1 Spoke can’t reach another spoke VPC route table missing remote CIDR → tgw-… describe-route-tables Add remote CIDR → TGW route in the subnet table
2 Route in VPC but still dark TGW attachment not associated to a route table describe-transit-gateway-route-tables assoc Associate the attachment
3 Association ok, CIDR invisible Route not propagated into the TGW table Check propagations Enable propagation (or add a static TGW route)
4 Return traffic dropped after appliance Appliance mode off → AZ asymmetry RA TGW_ATTACH_VPC_AZ_RESTRICTION Enable appliance mode on the inspection VPC attachment
5 On-prem via TGW fails TGW→VPN attachment or route missing Trace TGW route to vpn-… Add route; check VPN attachment
6 Jumbo across TGW drops 9001 > 8500 TGW cap Size-dependent loss Set instance MTU ≤ 8500 (or rely on TGW PMTUD)
7 Cross-account attachment dark RAM share not accepted / route missing Check RAM + TGW routes Accept share; add routes
8 SG-ref across TGW ignored Attachment lacks SG-referencing support RA SG_REFERENCING_SUPPORT Enable SG referencing on the attachment (same region) or use CIDRs

VPC peering

Peering is a 1:1, non-transitive link. If A peers B and B peers C, A cannot reach C through B — you need a direct A–C peering or a TGW. Every peering also needs routes on both sides and has hard limits worth memorising. Deep-dive: VPC Peering: Setup, Routing, Limits.

# Symptom Cause Confirm Fix
1 Out but never back Route added on one side only Check both VPCs’ tables for pcx-… Add the reverse route
2 A can’t reach C “through” B Peering is non-transitive Topology review Direct peering or TGW hub
3 Peering can’t be created Overlapping CIDRs Compare VPC CIDRs Re-CIDR one VPC; no overlap allowed
4 Cross-region SG-ref fails SG referencing not supported inter-region RA UNKNOWN_PEERED_SGS Use CIDR-based rules
5 Names don’t resolve across peer DNS resolution for peering not enabled Peering DNS options Enable DNS resolution on the peering
6 Only part of peer CIDR reachable More-specific route elsewhere Read the tables Fix prefixes
7 Jumbo inter-region drops Inter-region peering caps at 1500 Size-dependent MTU 1500 for inter-region
8 Edge-to-edge (on-prem) via peer fails Peering doesn’t carry VGW/endpoint routes Design review Don’t route on-prem/endpoints through peering

VPC endpoints

Endpoints give a private path to AWS services. Gateway endpoints (S3, DynamoDB) inject a prefix-list route into associated route tables; interface endpoints put an ENI (with its own SG) in your subnet and optionally hijack the service DNS name. Deep-dive: PrivateLink & VPC Endpoints.

# Symptom Cause Confirm Fix
1 S3 access times out Gateway-endpoint prefix-list route missing from the subnet’s table describe-route-tables for pl-… Associate the endpoint with the route table
2 AccessDenied via endpoint Restrictive endpoint policy Read the endpoint policy Widen the policy to the needed actions/resources
3 Interface endpoint name → public IP Private DNS off or enableDnsHostnames off dig service name Enable private DNS + DNS hostnames
4 Interface endpoint times out Endpoint ENI’s security group blocks 443 Check the endpoint SG Allow 443 from the client CIDR/SG
5 Endpoint works in one subnet only Interface endpoint not deployed in that AZ/subnet describe-vpc-endpoints subnets Add the endpoint ENI in the needed subnet
6 Cross-VPC service unreachable Gateway endpoints don’t traverse peering/TGW Design review Use interface endpoint / PrivateLink
7 Bucket policy blocks endpoint S3 bucket policy aws:SourceVpce mismatch Read bucket policy condition Add the correct VPCE id

On-prem via VPN / Direct Connect

Hybrid adds the most moving parts: two IPsec tunnels (for a VPN), BGP sessions, and the customer gateway. The dedicated build is Site-to-Site VPN Hybrid.

# Symptom Cause Confirm Fix
1 Total outage to on-prem Both tunnels DOWN describe-vpn-connections tunnel status Fix IKE/IPsec params; check CGW public IP
2 ~50% loss / intermittent One tunnel down, ECMP hashing to it Tunnel1 UP, Tunnel2 DOWN Restore the second tunnel
3 VPN up, no traffic BGP not advertising the CIDR (or static route missing) describe-vpn-connections routes / BGP Advertise on-prem CIDR; add VPC CIDR on-prem
4 Out but never back On-prem missing the return route to VPC CIDR On-prem routing table Add VPC CIDR route on-prem device
5 Bulk transfer stalls MSS/MTU over IPsec Works small, dies large Clamp MSS ≈1379; MTU 1500
6 Asymmetric through firewall Traffic exits one path, returns another; stateful firewall drops Compare forward vs return path Symmetric routing or appliance mode
7 DX down, VPN didn’t take over No backup VPN / route priority wrong Check DX VIF + backup Configure VPN backup with proper BGP metrics
8 SG/NACL blocks on-prem CIDR Firewalls not opened for on-prem ranges Check SG/NACL for on-prem CIDR Add rules for the on-prem CIDR
9 New VPC can’t reach on-prem TGW route table lacks the new attachment propagation TGW routes Propagate + associate the new attachment

The diagnostic toolkit: from silent timeout to named component

You do not guess your way down the layer list — you use tools that give a deterministic answer. There are four, and knowing which to reach for is half the skill.

Tool Answers Config or live? Best for Cost
Reachability Analyzer “Is there a path A→B, and if not, which component blocks it and why?” Config (static analysis) Route/SG/NACL/TGW/peering misconfig — the fastest first move ~$0.10 per analysis
Network Access Analyzer “What network paths exist to/from sensitive resources?” (audit) Config Finding unintended reachability at scale Per-analysis
VPC Flow Logs “Was this specific packet ACCEPTed or REJECTed, in which direction?” Live (observed traffic) Confirming a real drop, separating SG from NACL Ingestion + storage
Host tools (dig,nc,curl,traceroute,mtr,ss) “What does the instance itself see?” Live DNS, listener, RST vs timeout, path/MTU Free

Reachability Analyzer and its explanation codes

Reachability Analyzer is the first tool to reach for because it reasons about configuration and returns a verdict — Reachable or Not reachable — plus, when blocked, an explanation code naming the exact component and reason. It needs no live traffic, so you can run it before the app is even deployed. These are the codes you will actually see, grouped by layer (verbatim from the AWS reference):

Explanation code Layer Meaning Your fix
NO_ROUTE_TO_DESTINATION Route The route table has no applicable route to the destination Add the route to the subnet’s associated table
MORE_SPECIFIC_ROUTE Route A more-specific route matched and diverted the packet Adjust prefixes / expected intermediate component
HIGHER_PRIORITY_ROUTE Route A higher-priority route to the same CIDR wins Resolve the static-vs-propagated conflict
CANNOT_ROUTE Route The route’s CIDR/prefix-list doesn’t match the dest Correct the destination CIDR
BAD_STATE_ROUTE Route The route is in a non-functional (blackhole) state Repoint or delete the blackhole route
ENI_SG_RULES_MISMATCH SG The security group has no rule that applies Add the ingress/egress rule for the port/source
SG_HAS_NO_RULES SG The security group has no rules at all Add the required rules
SG_REFERENCES_NOT_PRESERVED SG Forwarded traffic lost its SG identity, breaking SG-ref rules Use CIDRs, or a fabric that preserves SG refs
UNKNOWN_PEERED_SGS SG/peering Cross-account/region peer SG is opaque → treated as deny Use CIDR-based rules across the peering
SUBNET_ACL_RESTRICTION NACL The subnet’s network ACL blocks the traffic Add the ordered ALLOW (+ ephemeral egress)
TGW_ATTACH_MISSING_TGW_RTB_ASSOCIATION TGW Attachment has no TGW route-table association Associate the attachment
TGW_RTB_NO_ROUTE_TO_TGW_ATTACHMENT TGW TGW route table has no route to this attachment Propagate/add the route in the TGW table
TGW_ATTACH_VPC_AZ_RESTRICTION TGW AZ asymmetry (appliance mode needed) Enable appliance mode
SG_REFERENCING_SUPPORT TGW Attachment doesn’t preserve SG references Enable SG referencing or use CIDRs
PCX_REQUIRES_ADDRESS_IN_VPC_CIDR Peering Address outside the peer VPC CIDR Fix the target address/CIDR
NGW_REQUIRES_SOURCE_IN_VPC NAT NAT can’t carry peering/VPN/DX-originated traffic Don’t route non-VPC traffic through NAT
VPCE_GATEWAY_PROTOCOL_RESTRICTION Endpoint Gateway endpoint only carries TCP/ICMP-echo Use the right protocol/endpoint type
NO_PATH / DISCONNECTED_VPCS Topology No path exists (unconnected VPCs, or unsupported feature like IPv6) Add the connection; note IPv6 is unsupported by RA
# Create and run a path analysis A → B on port 443, then read the verdict + explanation code.
PID=$(aws ec2 create-network-insights-path \
  --source i-0source --destination i-0dest \
  --protocol tcp --destination-port 443 \
  --query 'NetworkInsightsPath.NetworkInsightsPathId' --output text)

AID=$(aws ec2 start-network-insights-analysis \
  --network-insights-path-id "$PID" \
  --query 'NetworkInsightsAnalysis.NetworkInsightsAnalysisId' --output text)

aws ec2 describe-network-insights-analyses --network-insights-analysis-ids "$AID" \
  --query 'NetworkInsightsAnalyses[0].{Reachable:NetworkPathFound,Why:Explanations[].ExplanationCode}'
# Reachable=false with Why=["NO_ROUTE_TO_DESTINATION"] tells you the layer without touching a firewall.

VPC Flow Logs: proving the drop and naming the direction

When you need to see a real packet’s fate, Flow Logs record every accepted/rejected flow at the ENI or subnet. The trick to separating a security group from a NACL is to look at the direction pairing: a security-group block shows a REJECT with no matching return; a NACL ephemeral block shows an inbound ACCEPT paired with an outbound REJECT. The full field-by-field method is in VPC Flow Logs for Network Troubleshooting.

Field Example What it tells you
action ACCEPT / REJECT Whether SG/NACL let it through — the headline
srcaddr / dstaddr 10.0.1.10 / 10.0.2.50 Direction and the two ends
srcport / dstport 52344 / 443 Ephemeral vs service port (which side initiated)
protocol 6 (TCP) / 17 (UDP) / 1 (ICMP) Protocol — mismatched rules by protocol are common
packets / bytes 1 / 40 A single 40-byte SYN with no reply = handshake blocked
tcp-flags 2 (SYN) / 18 (SYN-ACK) SYN out, no SYN-ACK back = drop on the return or at dest
log-status OK / NODATA / SKIPDATA NODATA means no traffic matched — often no route
flow-direction ingress / egress Which side of the ENI — key to SG-vs-NACL
pkt-srcaddr original source behind NAT Real source when NAT/LB rewrote it
# Hunt REJECTs to a target's port in a Flow Logs CloudWatch group:
aws logs filter-log-events --log-group-name /vpc/flowlogs \
  --filter-pattern '[version,account,eni,src,dst,srcport,dstport="443",proto,pkt,byte,start,end,action="REJECT",status]' \
  --query 'events[].message' --output text

Host-level probes and describe-* commands

From the instance itself you get ground truth — the OS’s view of DNS, routes, and whether anything is listening. Two cheat-sheets you keep in muscle memory:

Command Use Read the result as
nc -vz host 443 Is the TCP port open from here? succeeded = reachable; timed out = drop; refused = RST
curl -v --max-time 5 https://host/ Full HTTP + TLS attempt Where it stalls (DNS, connect, TLS, response)
dig +short host @169.254.169.253 Resolve via the VPC resolver Empty/NXDOMAIN = DNS; public IP = private DNS off
traceroute -n -T -p 443 host Path + where it dies Last responding hop = boundary of reachability
mtr -T -P 443 host Continuous path + loss per hop Loss starting at hop N localises the break; intermittent = tunnel
ping -M do -s 1472 host PMTUD probe Fails big, works small = MTU
ss -ltnp Is anything listening locally? Empty for the port = RST source (not a network drop)
ip route get 10.0.2.50 Which route the OS picks Wrong egress = host routing, not VPC
describe-* call Confirms
describe-route-tables --filters …subnet-id The governing route table and its routes
describe-security-groups --group-ids The exact ingress/egress rules
describe-network-acls --filters …subnet-id Ordered NACL rules + the * deny
describe-instances …SourceDestCheck,SubnetId,PublicIp Public identity, AZ, appliance readiness
describe-network-interfaces Which SGs are on which ENI
describe-transit-gateway-route-tables / search-transit-gateway-routes TGW associations, propagations, routes
describe-vpc-peering-connections Peering state + accepter/requester CIDRs
describe-vpc-endpoints Endpoint type, subnets, policy, SG
describe-vpn-connections Tunnel status + advertised routes
describe-internet-gateways / describe-nat-gateways Gateway attachment + state

Architecture at a glance

The diagram below is the playbook rendered as the actual packet path: a request from source A first needs a route (badge 1), crosses the subnet’s stateless NACL (badge 2), then the ENI’s stateful security group (badge 3), and reaches a target that must be listening and answerable (badge 4). If B lives in another VPC or on-prem, the packet also traverses a Transit Gateway, peering, or VPN/DX (badge 5), where DNS and MTU also bite. Because every one of these drops is silent, you localise the real one at the diagnose stage (badge 6) with Reachability Analyzer’s explanation code and a Flow Logs REJECT. Read it left→right and, crucially, top-down through the numbers — that is the order you check the layers.

Packet-order connectivity path for AWS VPC troubleshooting: source A and its route table, then the subnet and stateless NACL, then the ENI security group and target instance, a branch to Transit Gateway/peering and VPN/Direct Connect, and a diagnose stage showing a reachable check versus a blocked REJECT, with six numbered badges marking route, NACL, security group, target, beyond-VPC, and diagnose failure points.

The single most valuable habit the diagram encodes: check in packet order and stop at the first layer that could be responsible. Nine times out of ten the answer is upstream of the security group you were about to open.

Real-world scenario

Company: Nimbus Retail, a mid-size e-commerce platform running a hub-and-spoke network in ap-south-1: a shared-services VPC (10.0.0.0/16), an order-service spoke (10.1.0.0/16), and a payments spoke (10.2.0.0/16), all attached to a Transit Gateway, with a Site-to-Site VPN from the shared VPC to an on-prem fraud-scoring system (192.168.0.0/16).

The incident. After a routine Terraform apply that “just added a subnet” to the order spoke, checkout latency spiked and ~40% of orders failed fraud scoring. The on-call engineer’s first move — the wrong first move — was to open the payments security group. It was untouched. An hour in, they widened it to 0.0.0.0/0 “to rule it out.” Nothing improved (and now there was a security hole to clean up later). The symptom was maddening: orders from some instances scored fine; others timed out. It looked intermittent, which sent everyone hunting a flaky app.

The systematic pass. A senior engineer ran the playbook in order. Reachability Analyzer from a failing order instance to the on-prem fraud endpoint returned Not reachable with NO_ROUTE_TO_DESTINATION. That immediately killed the security-group theory — the packet never reached a firewall. describe-route-tables on the new subnet showed it had been created without an explicit route-table association, so it fell back to the VPC main route table, which had no route for 192.168.0.0/16 → tgw-…. The “some instances work” pattern was simply which subnet an instance landed in: old subnets had the correct table; new-subnet instances used the main table and were dark to on-prem. One propagation was also missing on the TGW side, so even fixing the association only half-worked until they enabled propagation of the VPN attachment’s routes.

The fix and the lesson. Two changes: associate the new subnet with the order spoke’s private route table (which carries 192.168.0.0/16 → tgw), and enable route propagation for the VPN attachment in the TGW route table. Reachability Analyzer re-run: Reachable. Total fix time once they stopped guessing: 12 minutes. The postmortem action items were pure playbook discipline — never start at the security group; make Reachability Analyzer the first command; and in Terraform, always create aws_route_table_association for every subnet so nothing silently inherits the main table. Nimbus added a CI check that fails any plan creating an aws_subnet without a matching association. The security group they had opened to the world was reverted the same day — a reminder that “ruling out” a layer by weakening it creates a second incident.

Advantages and disadvantages

The “advantage” here is the method — systematic, packet-order, tool-first diagnosis — versus the ad-hoc alternative most teams default to.

Systematic playbook (this approach) Ad-hoc “change and re-test”
Deterministic: Reachability Analyzer names the component Guesswork: change a layer, wait, repeat
Checks in packet order → finds upstream causes first Anchors on the security group → misses routing
Non-destructive (analysis, not mutation) Often weakens firewalls “to rule out,” leaving holes
Minutes to localise Hours; frequently fixes the wrong layer
Repeatable across a team; teachable Lives in one person’s head
Works before deploy (config analysis) Needs live traffic to reproduce

The disadvantages are real and worth naming. Reachability Analyzer analyses configuration, not live packets — it will not catch an application bug, a host firewall (iptables), an OS that isn’t listening, or a transient tunnel flap, and it does not support IPv6 or every component type. Flow Logs prove real drops but cost money at scale and have a capture delay. Host tools give ground truth but require access to the instance (often the hardest thing to arrange mid-incident). The mature setup uses all four: RA to localise config, Flow Logs to confirm live behaviour, host tools for DNS/listener/MTU, and describe-* to read the exact rules — and never leans on only one.

Hands-on lab

You will deliberately break connectivity at the route-table layer, watch the silent timeout, localise it with Reachability Analyzer and Flow Logs, then fix it — in both aws CLI and Terraform — and tear it down. Free-tier friendly; the only meterable items are called out. Region: ap-south-1.

1. Build a single-instance internet-reachability target

# One VPC, one public subnet, an IGW, and a client instance with a public IP.
VPC=$(aws ec2 create-vpc --cidr-block 10.20.0.0/16 --query Vpc.VpcId --output text)
aws ec2 modify-vpc-attribute --vpc-id $VPC --enable-dns-hostnames
SUBNET=$(aws ec2 create-subnet --vpc-id $VPC --cidr-block 10.20.1.0/24 \
  --availability-zone ap-south-1a --query Subnet.SubnetId --output text)
aws ec2 modify-subnet-attribute --subnet-id $SUBNET --map-public-ip-on-launch
IGW=$(aws ec2 create-internet-gateway --query InternetGateway.InternetGatewayId --output text)
aws ec2 attach-internet-gateway --internet-gateway-id $IGW --vpc-id $VPC

# A purpose-built route table (never rely on the main table), associated + default route to the IGW.
RTB=$(aws ec2 create-route-table --vpc-id $VPC --query RouteTable.RouteTableId --output text)
aws ec2 associate-route-table --route-table-id $RTB --subnet-id $SUBNET
aws ec2 create-route --route-table-id $RTB --destination-cidr-block 0.0.0.0/0 --gateway-id $IGW

Launch a t3.micro client (CLIENT_ID) into that subnet — any AMI works; an SSM-enabled one is easiest so you need no bastion — then confirm the baseline from inside the instance:

# From the CLIENT instance (SSM Session Manager or SSH):
curl -s --max-time 5 https://checkip.amazonaws.com   # expected: your instance's public IP, e.g. 13.234.x.x

Expected: your public IP prints. The instance reaches the internet, so the security-group egress and the NACL are correct — any break you now introduce at the route layer is unambiguous.

Why the internet and not a second in-VPC server? Inside one VPC the local route (10.20.0.0/16) covers every subnet and cannot be removed or overridden — so you literally cannot break same-VPC routing at the route-table layer (that is the local-supremacy rule from Layer 1). Routing only matters, and only breaks, where a packet needs a non-local route: the internet, a peer, a TGW, or on-prem. So we break the one route that governs all of those — the default route.

2. Break it at the route table

# Delete the default route — the single most common route-table outage ("the instance lost the internet").
aws ec2 delete-route --route-table-id $RTB --destination-cidr-block 0.0.0.0/0
# From the CLIENT instance again:
curl -s --max-time 5 https://checkip.amazonaws.com   # expected NOW: hangs 5s, exits non-zero (silent timeout)

Note what you don’t see: no error explaining why, no refused, just a timeout. This is the moment most engineers open the security group. You will not — the SG never changed.

3. Localise with Reachability Analyzer

# Analyse the path from the instance to the internet gateway on 443.
PID=$(aws ec2 create-network-insights-path --source $CLIENT_ID --destination $IGW \
  --protocol tcp --destination-port 443 --query NetworkInsightsPath.NetworkInsightsPathId --output text)
AID=$(aws ec2 start-network-insights-analysis --network-insights-path-id $PID \
  --query NetworkInsightsAnalysis.NetworkInsightsAnalysisId --output text)
sleep 20
aws ec2 describe-network-insights-analyses --network-insights-analysis-ids $AID \
  --query 'NetworkInsightsAnalyses[0].{Reachable:NetworkPathFound,Why:Explanations[].ExplanationCode}'

Expected output:

{ "Reachable": false, "Why": [ "NO_ROUTE_TO_DESTINATION" ] }

There it is — the tool names the route layer, not the firewall, in one call and about ₹8 (US $0.10). You localised the break without touching a single rule.

4. Confirm with Flow Logs (optional, live proof)

Enable Flow Logs on the client ENI to a CloudWatch group, re-run the curl, and read the client ENI’s records:

aws logs filter-log-events --log-group-name /vpc/lab-flow \
  --filter-pattern '[v,acct,eni,src="10.20.1.*",dst,sp,dp="443",pr,pk,by,st,en,action,logst]' \
  --query 'events[].message' --output text

You will see the outbound SYN accepted at the ENI (the security group allowed egress) but no return record — the packet left the interface and was then dropped by the VPC router for lack of a route. That “it left, nothing came back” shape is the routing fingerprint; contrast it with a crisp REJECT, which would name a firewall instead. ⚠️ Flow Logs ingestion to CloudWatch is billable.

5. Fix it — CLI then Terraform

# Restore the default route; the fix is one line.
aws ec2 create-route --route-table-id $RTB --destination-cidr-block 0.0.0.0/0 --gateway-id $IGW
# Re-run Reachability Analyzer, then re-test:
aws ec2 start-network-insights-analysis --network-insights-path-id $PID --query NetworkInsightsAnalysis.State
# From the client:  curl -s --max-time 5 https://checkip.amazonaws.com  → your public IP again

The Terraform that makes this declarative and un-break-able — an explicit table, an explicit default route, and an explicit association so nothing silently inherits the main table:

resource "aws_route_table" "public" {
  vpc_id = aws_vpc.lab.id
}
resource "aws_route" "default" {                     # the route whose absence caused the outage
  route_table_id         = aws_route_table.public.id
  destination_cidr_block = "0.0.0.0/0"
  gateway_id             = aws_internet_gateway.lab.id
}
resource "aws_route_table_association" "public" {    # never rely on the main table
  subnet_id      = aws_subnet.public.id
  route_table_id = aws_route_table.public.id
}

Re-run Reachability Analyzer a final time; expect Reachable: true.

6. Teardown (do this — some resources meter)

aws ec2 delete-network-insights-analysis --network-insights-analysis-id $AID
aws ec2 delete-network-insights-path --network-insights-path-id $PID
aws ec2 terminate-instances --instance-ids $CLIENT_ID
aws ec2 wait instance-terminated --instance-ids $CLIENT_ID
aws logs delete-log-group --log-group-name /vpc/lab-flow          # stop Flow Logs billing
aws ec2 detach-internet-gateway --internet-gateway-id $IGW --vpc-id $VPC
aws ec2 delete-internet-gateway --internet-gateway-id $IGW
aws ec2 delete-subnet --subnet-id $SUBNET
aws ec2 delete-route-table --route-table-id $RTB
aws ec2 delete-vpc --vpc-id $VPC

⚠️ Costs to remember: Flow Logs ingestion and Reachability Analyzer (~₹8 per analysis) are the only meterable items here — you deliberately did not create a NAT gateway. EC2 t3.micro is free-tier eligible; the VPC, subnet, route table, IGW, SG and NACL are free.

Common mistakes & troubleshooting

This is the section you keep open during an incident. Start at the top of the master table, and for any symptom, check the layer it names before the ones below it.

The master symptom → layer → confirm → fix table

# Symptom Most likely layer Confirm (exact command / console) Fix
1 Timeout to a private IP in the same VPC SG ingress, then NACL describe-security-groups; describe-network-acls Add ingress for port/source; add NACL ALLOW + ephemeral egress
2 Timeout to another VPC (peered) Route both sides / non-transitive describe-route-tables both VPCs for pcx-… Add reverse route; direct peering not through a hub
3 Timeout to another VPC (via TGW) TGW association or propagation describe-transit-gateway-route-tables; VPC table for tgw-… Associate + propagate; add subnet route
4 Can’t reach the internet from private subnet NAT route / NAT AZ describe-route-tables for 0.0.0.0/0 → nat-… Add NAT route; per-AZ NAT
5 Can’t be reached from the internet Public IP/EIP absent describe-instances …PublicIp Attach EIP; enable auto-assign
6 Works in AZ-a, fails AZ-b Route-table association per AZ Compare the two subnets’ associated tables Associate AZ-b with a table that has the route
7 Inbound works, reply never returns NACL outbound ephemeral / return route Flow Logs pair; return route table Allow tcp 1024-65535 out; add return route
8 Connection refused (instant) Not the network — no listener / host FW ss -ltnp on the target Start the service; open host firewall
9 NXDOMAIN or wrong (public) IP DNS dig @169.254.169.253; check enableDnsHostnames Associate private zone; enable DNS hostnames/private DNS
10 Small requests work, large hang MTU / fragmentation ping -M do -s 1472 Lower MTU / clamp MSS; allow ICMP frag-needed
11 ~50% packet loss to on-prem One VPN tunnel down (ECMP) describe-vpn-connections tunnel status Restore second tunnel
12 On-prem totally unreachable Both tunnels / BGP Tunnel status + advertised routes Fix IKE/IPsec; advertise CIDRs
13 Out to on-prem but never back On-prem missing VPC return route On-prem device routing table Add VPC CIDR route on-prem
14 Response drops after appliance insertion Asymmetric routing / appliance mode off Compare forward vs return path; RA TGW_ATTACH_VPC_AZ_RESTRICTION Enable appliance mode; symmetric routing
15 S3/DynamoDB access times out Gateway-endpoint route missing describe-route-tables for pl-… → vpce-… Associate endpoint with the route table
16 S3 via endpoint returns AccessDenied Endpoint or bucket policy Read endpoint policy + bucket aws:SourceVpce Fix the policy/condition
17 Interface-endpoint name → public IP Private DNS / DNS hostnames off dig service-name Enable private DNS + DNS hostnames
18 Route exists but traffic vanishes Blackhole route (target deleted) Route State = blackhole Repoint/delete the route
19 Only part of a CIDR reaches a target More-specific route steals it Read the table; RA MORE_SPECIFIC_ROUTE Fix prefixes
20 Edited SG, “nothing changed” Connection tracking keeps old flows Test a new connection Wait for idle-out; don’t over-widen
21 Cross-region peer SG-ref denies SG referencing unsupported inter-region RA UNKNOWN_PEERED_SGS Use CIDR rules
22 IPv6 client fails, IPv4 works No ::/0 SG rule / eigw route Check v6 rules + routes Add v6 rules; egress-only IGW route
23 New instance briefly unreachable ENI not yet attached / warming RA UNASSOCIATED_COMPONENT / NO_PATH Wait; verify ENI attachment
24 Traffic ignores the firewall appliance local route supremacy inside VPC Read the table; local always wins Use ingress routing / more-specific outside local

Fast decision table

If you see… It’s probably… Do this first
A silent 30s timeout Route, SG, or NACL (in that order) Reachability Analyzer A→B
Instant connection refused The host — no listener / host firewall ss -ltnp on the target
No route to host locally Source OS routing / subnet association ip route get; describe-route-tables
Works small, hangs large MTU ping -M do -s 1472
Intermittent / ~50% One dead tunnel or asymmetric ECMP describe-vpn-connections; appliance mode
One AZ good, one AZ bad Per-AZ route-table association / NAT Compare the subnets’ tables
Out but never back Return route or NACL outbound Return route table; ephemeral egress
Name won’t resolve DNS dig @169.254.169.253
AccessDenied (not timeout) Policy (endpoint/bucket/IAM), not network Read the policy
Reachable in RA but still fails live Host FW, listener, or app Host tools + app logs

The three nastiest, in prose

Asymmetric routing through an appliance. You insert a firewall/IDS appliance (or a GWLB) and traffic mysteriously drops on the return leg. The forward packet goes A→appliance→B, but the reply from B takes a different path back (a different AZ’s appliance ENI, or straight via local), and the stateful appliance never saw the forward flow so it drops the reply. The fingerprint: SYN arrives, SYN-ACK leaves, connection never completes; it often works when A and B happen to land in the same AZ and fails across AZs. On a Transit Gateway the cure is appliance mode (which pins both directions to the same appliance ENI); RA flags it as TGW_ATTACH_VPC_AZ_RESTRICTION. Off the TGW, the cure is symmetric routing — force the return through the same appliance with more-specific routes, because the VPC local route will otherwise steal in-VPC replies straight back.

“Works from one AZ, not another.” Nothing about the app differs, yet instances in AZ-a succeed and AZ-b times out. This is almost always per-AZ routing or NAT: the AZ-b subnet is associated with a route table that lacks the NAT/TGW route the AZ-a subnet has, or there is a single NAT gateway in AZ-a and AZ-b’s route points at a NAT that was deleted (blackhole), or an interface endpoint/appliance exists only in AZ-a’s subnet. Confirm by diffing the two subnets’ associated route tables and the AZ placement of NATs/endpoints. The permanent fix is architectural: one route table and one NAT (and one endpoint ENI) per AZ, every subnet explicitly associated — never rely on the main table.

Intermittent = MTU or one dead tunnel. Two very different causes share the “sometimes works” signature. If failures correlate with payload size (small OK, large stalls), it is MTU: a DF-set jumbo packet meeting a 1500/8500 hop with PMTUD’s ICMP blocked. If failures correlate with which flow/hash rather than size (roughly half of connections fail, retries sometimes succeed), it is one of the two VPN tunnels down while ECMP keeps hashing new flows onto the dead one — or an asymmetric multi-path. Separate them fast: ping -M do -s 1472 proves MTU; describe-vpn-connections showing Tunnel1: UP, Tunnel2: DOWN proves the tunnel. Treating an MTU black hole as “flaky network” (or a dead tunnel as “app instability”) is the classic multi-hour rabbit hole.

Best practices

Security notes

Troubleshooting connectivity and hardening it pull in opposite directions, and the seam is where mistakes happen. First, do not debug by widening: the 0.0.0.0/0 “temporary” rule is the most common way a connectivity ticket turns into a data-exfiltration path — use Reachability Analyzer (which needs no rule changes) instead, and if you must widen, scope to the exact source CIDR and revert in the same session. Second, treat Reachability Analyzer and Network Access Analyzer as security tools too: Network Access Analyzer specifically audits unintended reachability — run it to prove that a database subnet is not reachable from the internet, not just that your app is reachable. Third, least-privilege the diagnostic access itself: the IAM permissions to run ec2:StartNetworkInsightsAnalysis, read Flow Logs, and describe-* should be a scoped read/analyse role, not admin. Fourth, VPC endpoint and bucket policies are a legitimate “deny” — an AccessDenied through an endpoint is often the control working; confirm intent before “fixing” it, and use aws:SourceVpce conditions to keep S3 access private-path-only. Finally, encrypt the transport you are debugging (TLS end-to-end, IPsec on the VPN) so that the packet captures and Flow Logs you generate during an incident never expose payload data — Flow Logs record metadata, but the habit of grabbing tcpdump mid-incident can.

Cost & sizing

The diagnostic tooling is cheap relative to an hour of engineer time, but Flow Logs and NAT data can surprise you at scale. Rough figures (USD list; INR ≈ ×83):

Item What drives it Rough cost Notes
Reachability Analyzer Per analysis run ~$0.10 (₹8) Trivial; run it freely
Network Access Analyzer Per ENI analysed Per-analysis Scheduled audits add up at fleet scale
VPC Flow Logs → CloudWatch GB ingested + stored ~$0.50/GB ingest + storage The real cost; sample or send to S3 for cheaper
VPC Flow Logs → S3 GB delivered + stored Cheaper than CWL Use for high-volume, query with Athena
NAT gateway Hourly + per-GB processed ~$0.045/hr + ~$0.045/GB Per-AZ NAT multiplies the hourly; consolidate carefully
Cross-AZ data GB across AZs ~$0.01/GB each way Single-AZ NAT for multi-AZ subnets incurs this
Transit Gateway Attachment/hr + per-GB ~$0.05/attach/hr + data Data processing on every inter-VPC GB
VPN connection Connection-hour + data ~$0.05/hr Two tunnels, one connection charge
Reachability Analyzer + RA in CI Per PR analysis ~$0.10 each Cheap insurance against merge-time breakage

Sizing guidance: enable Flow Logs at the VPC level with a sampled or aggregated format for always-on visibility, and switch to full capture on a specific ENI only during an incident to control ingest cost. Send high-volume Flow Logs to S3 + Athena rather than CloudWatch Logs; the query experience is nearly as good and the storage is far cheaper. For NAT and TGW, the bill is dominated by data processing, so a connectivity “fix” that accidentally routes bulk traffic through a NAT or across a TGW when a VPC endpoint would do can quietly add real money — another reason to know exactly which path your packet takes.

Interview & exam questions

Q1. A packet from A times out reaching B in the same VPC. In what order do you check the layers, and why? Route table first (no route = dropped before any firewall), then security group ingress, then NACL, then whether the target is listening. You go in packet order because each layer can produce the identical silent timeout, and the earliest one that could be responsible must be cleared first. (ANS-C01/SAA-C03)

Q2. What is the difference between a Connection timed out and a Connection refused, diagnostically? Timed out = a stateful firewall or routing black hole dropped the packet silently; the packet may never have reached the host. Refused = an instant TCP RST, meaning the packet did reach the host but nothing is listening on that port (or a host firewall sent the RST). Refused is proof the network path works. (SOA-C02)

Q3. Why does a custom NACL so often break return traffic? NACLs are stateless, so the reply — which leaves from the server toward the client’s high ephemeral port — must be explicitly allowed by an outbound rule. If the NACL only allows outbound 443, the reply on ports 1024–65535 hits the implicit * deny and is dropped; the client just times out. Fix: outbound tcp 1024-65535 to the client CIDR. (SAA-C03)

Q4. Reachability Analyzer returns NO_ROUTE_TO_DESTINATION. What does that eliminate, and what do you check? It eliminates the security group and NACL as the first cause — the packet has no route, so it dies before any firewall. Check the source subnet’s associated route table for a matching route to the destination CIDR, and confirm the subnet isn’t silently using the main table. (ANS-C01)

Q5. Explain association vs propagation on a Transit Gateway. Association binds an attachment to one TGW route table that governs traffic leaving that attachment. Propagation advertises an attachment’s CIDRs into a TGW route table so others can route to it. You need both: association without propagation means the attachment can send but is invisible as a destination. (ANS-C01)

Q6. Why is VPC peering “non-transitive,” and what breaks because of it? Each peering is a 1:1 link that only routes between the two VPCs’ CIDRs; it does not forward to a third VPC. So a hub-and-spoke built from chained peerings fails spoke-to-spoke. Use a Transit Gateway for any-to-any. (SAA-C03)

Q7. A transfer works for small files and stalls for large ones across a VPN. Diagnose. Path MTU: the VPN caps at 1500 with IPsec overhead, so DF-set large packets are dropped, and if the ICMP fragmentation-needed message is blocked, PMTUD never shrinks the sender. Confirm with ping -M do -s 1472; fix by clamping TCP MSS (~1379) or lowering MTU and allowing ICMP type 3 code 4. (ANS-C01)

Q8. “Works from AZ-a, not AZ-b.” Top hypothesis? The AZ-b subnet is associated with a route table missing a route AZ-a’s has (commonly the NAT or TGW route), or a NAT/endpoint/appliance exists only in AZ-a. Diff the two subnets’ associated route tables and the AZ placement of NAT/endpoints. (SOA-C02)

Q9. What does an interface-endpoint name resolving to a public IP tell you? Private DNS on the endpoint (or enableDnsHostnames on the VPC) is off, so the service name resolves to the public regional endpoint instead of the private ENI. Enable private DNS and DNS hostnames. (ANS-C01)

Q10. How do you use Flow Logs to distinguish a security-group block from a NACL block? Pair the directional records: a security-group drop shows a REJECT with no established return; a NACL ephemeral omission shows an inbound ACCEPT paired with an outbound REJECT to the client’s high port. The flow-direction and action fields together localise the layer. (SOA-C02)

Q11. Why can’t you force in-VPC traffic through an appliance using only the main route table? The local route for the VPC CIDR always wins inside the VPC and cannot be overridden by a route to an ENI for an address within that CIDR. You need gateway/ingress routing or a more-specific route outside the local scope (or place the appliance on the north-south path). (ANS-C01)

Q12. When is AccessDenied (not a timeout) the correct behaviour? When a VPC-endpoint policy, S3 bucket policy aws:SourceVpce condition, or IAM policy is intentionally denying the action — that is an authorization control working, not a connectivity failure. Confirm intent before changing it. (SCS-C02)

Quick check

  1. A connection returns an instant Connection refused. Is this a security-group problem? Why or why not?
  2. In what order does a packet cross the route table, security group, and NACL on the way in?
  3. You get MORE_SPECIFIC_ROUTE from Reachability Analyzer. What is happening?
  4. What single outbound NACL rule prevents the most common firewall block, and why?
  5. Failures correlate with payload size, not with which flow. What layer, and what one command confirms it?

Answers

  1. No. Connection refused is an instant RST from the host, which means the packet crossed every firewall and reached a listening host that had nothing on that port — the network path works. Check ss -ltnp and the app, not the security group.
  2. NACL (subnet edge) → security group (ENI) → instance. The stateless NACL is the outer gate, the stateful SG is the inner gate; the route table decides whether the packet gets to the subnet at all, before either firewall.
  3. A route more specific than the one you expected is matching the destination and diverting the packet elsewhere (longest-prefix match). Read the route table and fix the prefixes or add the expected intermediate component.
  4. Outbound tcp 1024-65535 to the client CIDR. NACLs are stateless, so the reply leaves on the client’s high ephemeral port and must be explicitly allowed, or it hits the implicit * deny and the client times out.
  5. MTU / fragmentation. ping -M do -s 1472 <dest> — if the 1472 (→1500) probe fails but -s 1400 succeeds, a hop on the path can’t carry 1500 bytes and PMTUD is being black-holed.

Glossary

Term Definition
Silent drop A packet discarded with no response, producing a client-side timeout — the default behaviour of route/SG/NACL drops
RST (reset) A TCP reset from the host = connection refused; proof the packet reached a host with no listener (not a network drop)
Longest-prefix match Routing rule where the most specific matching CIDR wins; a /24 beats a /0
Blackhole route A route whose target no longer exists; it still matches and silently drops traffic (State: blackhole)
Ephemeral port range The high source ports (≈1024–65535) a client uses for a connection; NACLs must allow them outbound for replies
Stateful vs stateless SGs remember connections (replies auto-allowed); NACLs don’t (each direction judged independently)
Reachability Analyzer Config-based path analysis that returns reachable/not plus an explanation code naming the blocking component
Explanation code The machine-readable reason RA returns, e.g. NO_ROUTE_TO_DESTINATION, SUBNET_ACL_RESTRICTION, ENI_SG_RULES_MISMATCH
Flow Logs Records of accepted/rejected flows at ENI/subnet/VPC scope, used to prove and localise a real drop
Association (TGW) Binds an attachment to the TGW route table that governs its outbound traffic
Propagation (TGW) Advertises an attachment’s CIDRs into a TGW route table so others can reach it
Non-transitive Property of VPC peering: A–B and B–C do not yield A–C; each pair needs a direct link
Appliance mode A TGW attachment setting that pins both directions of a flow to the same appliance ENI, preventing AZ asymmetry
PMTUD Path MTU Discovery — TCP shrinks packet size using ICMP “fragmentation needed”; blocking that ICMP black-holes large packets
MSS clamping Rewriting the TCP MSS on SYN so payloads never exceed the smallest MTU on the path (used on VPN/DX)

Next steps

AWSVPCReachability AnalyzerVPC Flow LogsRoute TablesTransit GatewayMTUTroubleshooting
Need this built for real?

Vinod is a Senior Cloud Architect (22+ yrs) — available for Azure / AWS / GCP architecture, landing zones, and migrations.

Work with me

Comments

Keep Reading