IPv6 in AWS: it’s decided by routing and security groups, not the address
Turning on IPv6 in AWS is not “IPv4 with longer addresses.” There is no NAT and no private range — a subnet is public or private purely by how you route ::/0 — security groups don’t inherit your IPv4 rules, IPv6-only subnets still need DNS64 + NAT64 to reach IPv4, and the reason to do it now is that public IPv4 bills by the hour.
Scope: AWS VPC IPv6 — addressing, gateways, DNS64/NAT64, security, dual-stack vs IPv6-only, and the edge load balancer. Verified against AWS docs, July 2026. Transit Gateway / Cloud WAN IPv6, on-prem / Direct Connect IPv6, and a per-service support matrix are their own articles.
You add an Amazon-provided /56 to your VPC, give a subnet a /64, launch an instance — and it answers a ping from your laptop before you have finished your coffee. No NAT gateway, no “auto-assign public IP” checkbox, no private range to hide behind. IPv6 in AWS quietly breaks five things you know cold about IPv4, and each break is a place teams get burned. Here is the whole model in one picture, then each break in turn.
TL;DR — IPv6 in AWS, in five breaks
- There is no NAT and no private range. Every address is a globally-routable GUA; whether a subnet is public or private is decided by the route table (and the security group), not by the address.
::/0 → internet gatewayis public-by-default. An IPv6 instance is reachable the moment it boots. For outbound-only, the egress-only internet gateway replaces the NAT gateway’s job — and does no address translation, only direction.- IPv6-only subnets still need IPv4 for the IPv4-only internet and some AWS endpoints — via DNS64 (synthesizes AAAA with the
64:ff9b::/96prefix) + NAT64 (automatic on every NAT gateway). One shared IPv4, not one per host. - Security groups are per-address-family. Your IPv4 rules do nothing for IPv6; write the
::/0rules again, or you fail open (public-by-default) or fail closed (no rule answers). - Do it now because IPv4 costs money. Since Feb 2024 every public IPv4 bills at $0.005/hr (~$3.60/mo each). Dual-stack to migrate safely; go IPv6-only to actually stop paying.
Why now: IPv4 costs money
Since February 1, 2024, AWS charges $0.005 per hour for every public IPv4 address — attached or idle, on an instance, a NAT gateway, a load balancer, an RDS endpoint, anything. That is about $3.60 a month per address. On one box it is a rounding error; across a fleet of thousands of ENIs, NAT gateways, and load balancers it is a real line item, and it only climbs as IPv4 gets scarcer — the market price of an address is up more than 300% in five years. IPv6 addresses are free. That one line turned IPv6 from “someday” into “this quarter.” The second driver is quieter: large orgs run out of RFC 1918 space (all of 10.0.0.0/8 is 16 million addresses) and hit overlapping-CIDR pain in every merger and peering; IPv6’s practically unbounded space deletes that whole class of problem.
The whole model in one VPC
Everything IPv6 does inside a VPC fits in one diagram. You get one Amazon-provided /56 and carve it into /64 subnets — always a /64, that is the AWS unit. Three kinds of subnet, distinguished only by where their default route points: a public subnet routes ::/0 to an internet gateway; a private-egress subnet routes ::/0 to an egress-only internet gateway; and an IPv6-only subnet adds a 64:ff9b::/96 route to a NAT gateway so it can still reach IPv4. A dual-stack load balancer sits at the edge for IPv4 clients. Hold this picture — the next five sections each grey out everything but one part of it.
1 · There is no private range
In IPv4 you learned that 10.x / 172.16.x / 192.168.x are private and everything else is public — the address told you the reach. IPv6 in AWS throws that away. Every address AWS hands out is a GUA (Globally Unique Address): globally routable, unique, with no NAT in front of it. There is no “private IPv6 range you use everywhere” (the protocol has ULA, fd00::/8, but AWS VPC addressing is GUA). So the address no longer tells you whether something is reachable. Reachability is decided by two things: the route table and the security group. Same instance, same address — public or private depending only on where ::/0 points and what the SG allows.
| Your IPv4 assumption | The IPv6 reality in AWS |
|---|---|
“10.x is private” | No private range — every address is a globally-routable GUA |
| “NAT hides my instances” | There is no NAT for IPv6 — routing hides them |
| “No public IP = not reachable” | Reachable if ::/0 → IGW and the SG allows it |
| “Size the subnet to need (/24, /26…)” | Always a /64 per subnet — fixed |
2 · Public vs private is a routing decision
With no NAT and no private range, what makes a subnet public? One route. Point ::/0 at an internet gateway and the subnet is public — inbound and outbound. This is the surprise that bites hardest: an IPv6 instance there is reachable from the internet the moment it boots, with no “assign public IP” toggle to forget and no NAT to sit behind. Public-by-default.
For outbound-only — the classic “private instances that need to reach the internet” — point ::/0 at an egress-only internet gateway. It is stateful: return traffic for connections the instance opened is allowed; new inbound is dropped. This is the IPv6 stand-in for the NAT gateway’s job, with one critical difference: it does no address translation. The instance keeps its own GUA; the egress-only gateway only enforces direction. (It is also free, where a NAT gateway is not.) Point ::/0 at nothing and the subnet is isolated.
3 · IPv6-only still needs IPv4 — via DNS64 and NAT64
An IPv6-only subnet has no IPv4 at all — the smallest possible blast radius for address exhaustion. But much of the internet, and a few AWS endpoints, are still IPv4-only, and an IPv6-only host cannot speak to them directly. Two features bridge the gap. DNS64, enabled per subnet on the Route 53 Resolver, watches DNS answers: when a name has only an A record, the resolver synthesizes an AAAA by prefixing the well-known 64:ff9b::/96 to the IPv4 address. The host now has an IPv6 address to send to. NAT64 does the translation: traffic to a 64:ff9b::/96 address is routed to a NAT gateway, which swaps IPv6 for IPv4 on the way out. NAT64 is automatic on every NAT gateway — nothing to enable; you just add the route. The honest seam: the NAT gateway still uses a public IPv4 (billable) — but one shared address for the whole subnet, not one per instance.
::/0 → egress-only IGW (native IPv6 out) · 64:ff9b::/96 → NAT gateway (IPv4 via NAT64) · DNS64 enabled on the subnet.4 · Security groups don’t carry over
This is the quiet one. Security groups and network ACLs are per-address-family: your existing IPv4 rules do nothing for IPv6, and the default security group has no ::/0 allow. That fails in both directions. Fail-closed: you turn on IPv6, the service simply does not answer over it because no rule permits it, and you lose an afternoon before remembering SGs are family-specific. Fail-open: you add a broad ::/0 allow to “make it work,” and because IPv6 is public-by-default you have just exposed more than you meant to. The rule is blunt: every IPv4 rule that should also apply to IPv6 must be written again for IPv6, and audited in both families. NACLs too — a custom NACL written for IPv4 silently drops IPv6 unless you add ::/0 entries.
| Failure | Cause | Fix |
|---|---|---|
| Fail-closed — service silent over IPv6 | Added IPv6 addressing + routes, but no SG rule for ::/0 | Add the matching ::/0 ingress rules (both SG and NACL) |
| Fail-open — more exposed than intended | Added a broad ::/0 allow to unblock it; IPv6 is public-by-default | Scope the ::/0 rule to the ports and sources you actually meant |
Dual-stack or IPv6-only? Pick per subnet
Two ways to run a subnet. Dual-stack (both IPv4 and IPv6) is the safe migration path: everything keeps working, you add AAAA records and IPv6 routes alongside the IPv4 ones. But you still pay for public IPv4 and still consume RFC 1918 space — you have added IPv6, not solved either driver. IPv6-only actually solves cost and exhaustion, but it demands that every workload, AMI, and agent in the subnet is IPv6-capable (some old software binds IPv4-only), plus DNS64/NAT64 for the IPv4 world. EC2 on Nitro, EKS (IPv6 cluster mode, prefix mode, Nitro-only; security-groups-for-pods since 2023), and ECS (IPv6-only GA September 2025) all support it. The rule: dual-stack to migrate safely; go IPv6-only on new, exhaustion-sensitive fleets you control — large EKS/ECS estates — and keep a dual-stack load balancer at the public edge, because too much of the client internet is still IPv4 to make your front door IPv6-only. The edge LB translates IPv4 clients to your IPv6 backends.
| Dual-stack | IPv6-only | |
|---|---|---|
| Solves IPv4 cost / exhaustion? | No — still holds public IPv4 | Yes |
| Reaches IPv4-only endpoints | Natively | Via DNS64 + NAT64 |
| Requires IPv6-ready software | No | Yes — every workload, AMI, agent |
| Best for | Migrating safely | New exhaustion-sensitive fleets you control |
The migration order
The order matters because one step — routing — is what makes things reachable. Do it after security, never before.
- Add an Amazon-provided
/56to the VPC (IPv4 keeps working). - Add a
/64to each subnet; enable auto-assign where you want it. - Write the IPv6 security-group and NACL rules first — before routing, so nothing is unintentionally open.
- Add routes:
::/0 → IGW(public) or→ egress-only IGW(private-out);64:ff9b::/96 → NAT gateway+ DNS64 for IPv6-only. - Add
AAAArecords; make the load balancers dual-stack. - Only then flip subnets to IPv6-only where the win is worth it and the workloads are ready.
Where it bites
| Gotcha | Why | Fix |
|---|---|---|
| Instance unexpectedly reachable from the internet | ::/0 → IGW is public-by-default | Use an egress-only IGW for outbound-only; scope the SG |
| Service silent over IPv6 | Forgot the IPv6 SG / NACL rules | Rewrite every relevant rule for ::/0 |
| IPv6-only workload can’t reach a dependency | An IPv4-only endpoint with no NAT64 route | Add 64:ff9b::/96 → NAT gateway + DNS64 |
| “The egress-only gateway will translate addresses” | It does not — direction only | Use a NAT gateway (NAT64) for translation |
| Still paying for IPv4 after “adding IPv6” | You dual-stacked but never removed public IPv4 | Concentrate IPv4 at the edge, or go IPv6-only |
IPv6 in AWS is less a new protocol to learn than a set of IPv4 reflexes to un-learn. Keep one rule in front: the address tells you nothing — the route table and the security group decide everything. Get those two right, and the rest is just choosing where to stop paying for IPv4.
References
- AWS — New: AWS Public IPv4 Address Charge + Public IP Insights (Feb 2024): aws.amazon.com/blogs/aws/new-aws-public-ipv4-address-charge
- Amazon VPC User Guide — IPv6 addressing, Egress-only internet gateways, DNS64 and NAT64: docs.aws.amazon.com/vpc/latest/userguide/nat-gateway-nat64-dns64.html
- Amazon EKS — Running IPv6 clusters (prefix mode, Nitro-only; security groups for IPv6 pods, 2023): docs.aws.amazon.com/eks/latest/userguide/cni-ipv6.html
- AWS — Amazon ECS announces IPv6-only support (Sep 2025): aws.amazon.com/blogs/containers/amazon-ecs-announces-ipv6-only-support