Lab 15: The Half Of The Internet That Was Missing
IPv6 was enabled on the VPC. Instances have global IPv6 addresses and an IPv6 default route. IPv4 works, IPv6 connections time out, and every dual-stack destination is quietly slower than it was last week.
- Debugging time
- ~20 min
- Reading time
- 9 min
- Reported by
- Integrations
- Tier
- Associate
Partner API over IPv6 times out; unrelated services got slower the same day
Reported by Integrations
We enabled IPv6 on this VPC last week so we could reach a partner API that publishes AAAA records only. Calls to it time out.
IPv6 is definitely on. The VPC has an IPv6 CIDR, the subnet has one, and the instances have real global
IPv6 addresses — I can see them in ip addr. The instance even has an IPv6 default route. Security
groups allow all egress on both families. The internet gateway is attached.
Second thing, possibly unrelated: since the change, a few of our other outbound calls have been slightly slower. Not failing, just slower. Maybe a couple of hundred milliseconds. Nobody can explain it and it might be noise.
What you are working with
One dual-stack VPC, one Availability Zone.
| Resource | Configuration |
| --- | --- |
| VPC | 10.130.0.0/16 and an Amazon-provided IPv6 /56 |
| Subnet | 10.130.1.0/24 and an IPv6 /64, both auto-assigned on launch |
| Internet gateway | Attached |
| Route table | 0.0.0.0/0 → internet gateway |
| Security group | All egress on 0.0.0.0/0 and ::/0 |
| Instances | Global IPv6 addresses, IPv6 default route from the VPC router |
Read the route table row once more and note what is not in it.
- EC2
Client instance
has a global IPv6 address and an IPv6 default route
- FILTER
Security group egress
::/0 permitted
- RTB
Subnet route table
IPv6 routes: the local /56 only
Dropped — No ::/0 route exists, so no route matches an off-VPC IPv6 destination and the packet is discarded. The guest OS has already committed to IPv6 because the VPC router advertised a default route.
- GW
Internet gateway
handles IPv6 fine, never reached
The instance is right that it has a default route. That route is the guest's, learned by router advertisement. The VPC route table is a separate thing and has no IPv6 default.
Scope and constraints
- In scope: why IPv6 traffic does not leave the VPC.
- Out of scope: security groups, the internet gateway, address assignment, and DNS. All correct.
- The instances really do have global IPv6 addresses and an IPv6 default route. The reporter is right.
- The "unrelated" slowness is not unrelated. Explaining it is part of the lab.
Deploy the broken state
cd lab-15-ipv6-default-route
terraform init
terraform apply
aws ssm start-session --target "$(terraform output -raw client_instance_id)"Session Manager works throughout, because the agent talks to AWS over IPv4.
Confirm IPv6 looks configured
Everything the reporter said is true, and this is worth establishing rather than assuming:
ip -6 addr show scope global
# 2: ens5: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001
# inet6 2600:1f18:abcd:1200:6d3b:...:9f01/128 scope global dynamicip -6 route show default
# default via fe80::4a5:6bff:fe12:3456 dev ens5 metric 1024 expires 1799secA global address and a default route. The instance is fully prepared to use IPv6 — which is exactly why it does, and why the packets go nowhere.
Confirm IPv6 fails off the VPC
curl -6 -s -m 8 -o /dev/null -w '%{http_code}\n' https://ipv6.google.com/
# 000A timeout, not an error. Note that distinction: Network is unreachable would mean the local kernel had
no route and refused to send. A timeout means the packet left the instance and nothing came back.
ping6 -c 3 -W 3 2606:4700:4700::1111
# 3 packets transmitted, 0 received, 100% packet lossConfirm IPv6 works inside the VPC
This is the step that localises the fault.
PEER=$(terraform output -raw peer_ipv6)
ping6 -c 3 "$PEER"
# 3 packets transmitted, 3 received, 0% packet loss
nc -6 -w 3 "$PEER" 5000
# lab-15-ipv6-in-vpc-okIPv6 is working. The address family is configured correctly, the interface is up, the security group permits it, and two hosts can talk over it. What fails is leaving the VPC.
That combination — a family that works locally and not remotely — points at exactly one place.
Explain the "unrelated" slowness
Time a request to a dual-stack host, first letting the client choose, then forcing each family:
curl -s -o /dev/null -w 'default: %{time_total}s\n' https://www.cloudflare.com/
# default: 0.462s
curl -4 -s -o /dev/null -w 'ipv4: %{time_total}s\n' https://www.cloudflare.com/
# ipv4: 0.241s
curl -6 -s -m 8 -o /dev/null -w 'ipv6: %{time_total}s\n' https://www.cloudflare.com/
# ipv6: (times out)The default is roughly 200 ms slower than forcing IPv4. That is Happy Eyeballs doing its job: curl
resolves both families, starts the IPv6 attempt first because AAAA is preferred, waits out its
happy-eyeballs timeout — 200 ms by default — then starts IPv4 in parallel and uses whichever connects.
So every dual-stack destination pays a fixed penalty while the doomed IPv6 attempt is abandoned. The reporter's "couple of hundred milliseconds" is precisely the fingerprint of a broken IPv6 path behind a working IPv4 one.
This also explains why nothing looked broken. Most destinations are dual-stack, Happy Eyeballs covers them, and only the IPv6-only partner API fails outright.
Read the route table's IPv6 entries
aws ec2 describe-route-tables --region us-east-1 \
--route-table-ids "$(terraform output -raw route_table_id)" \
--query 'RouteTables[0].Routes[].[DestinationCidrBlock,DestinationIpv6CidrBlock,GatewayId,State]' \
--output table
# ---------------------------------------------------------------------------
# | 10.130.0.0/16 | None | local | active |
# | None | 2600:1f18:abcd:1200::/56 | local | active |
# | 0.0.0.0/0 | None | igw-0a1b… | active |
# ---------------------------------------------------------------------------Three routes. Two local, one IPv4 default. There is no ::/0.
The IPv6 local route was created automatically when the CIDR was associated, which is why the table looks like it has IPv6 in it. Local routes cover the VPC's own prefixes and nothing else — which is consistent with IPv6 working between the two instances and failing everywhere else.
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.