AWS Cloud · Networking

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.

Primer·platform & network engineers·

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 gateway is 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::/96 prefix) + 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 ::/0 rules 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.
Part 1Why, and the model

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 number. $0.005/hr × ~730 hrs ≈ $3.60/month per public IPv4. The EC2 free tier covers 750 hours/month for the first 12 months; addresses you bring yourself (BYOIP) are not charged.
Why now: public IPv4 bills by the hour ($0.005/hr since Feb 1, 2024)~$3.60 per address per month — the driver is to stop holding one per instanceIPv4 today — one public IP per instanceinstance$0.005/hrpublic IPv4 #1instance$0.005/hrpublic IPv4 #2instance$0.005/hrpublic IPv4 #3instance$0.005/hrpublic IPv4 #4N instances = N × $3.60 / month (and rising)IPv6 — free addresses, one shared IPv4instanceIPv6 · freeGUA — no public IPv4instanceIPv6 · freeGUA — no public IPv4instanceIPv6 · freeGUA — no public IPv4instanceIPv6 · freeGUA — no public IPv4NAT64 / dual-stack edge1 × $3.60 / month — shared by the whole subnetIPv6
Figure 1 · Public IPv4 now bills by the hour. Left: one address per instance, N × $3.60/month. Right: IPv6 everywhere (free) with a single shared IPv4 at the NAT64 / edge — the bill collapses to one.

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.

IPv6 in AWS — the whole model in one VPCpublic = ::/0→IGW · outbound-only = ::/0→EIGW · IPv6-only reaches IPv4 via DNS64 + NAT64InternetIPv6 + IPv4VPC · 2001:db8::/56Dual-stack LBedge · v4+v6 inPublic subnet /64 · ::/0 → IGWPrivate-egress /64 · ::/0 → EIGWIPv6-only /64 · ::/0→EIGW + NAT64SG · per address-familyInstanceGUA · reachable at bootSG · per address-familyInstanceoutbound onlySG · per address-familyInstanceIPv6-only · no IPv4R53 · DNS64synth AAAAInternet GW::/0 in + outEgress-only GW::/0 outboundNAT gatewayNAT64inbound + outbound — public by defaultoutbound only · no translation64:ff9b::/96
Figure 2 · A dual-stack / IPv6 VPC. One /56, three /64 subnets differing only by where ::/0 routes; DNS64 + NAT64 let the IPv6-only subnet reach IPv4; security-group rings guard every instance.
That is the anatomy. Now the five breaks — each is a place an IPv4 habit misfires.
Part 2The five breaks

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.

Addressing: one /56, /64 per subnet, every address a GUAthere is no private range — reachability is the route table, not the addressInternetIPv6 + IPv4VPC · 2001:db8::/56Dual-stack LBedge · v4+v6 inPublic subnet /64 · ::/0 → IGWPrivate-egress /64 · ::/0 → EIGWIPv6-only /64 · ::/0→EIGW + NAT64SG · per address-familyInstanceGUA · reachable at bootSG · per address-familyInstanceoutbound onlySG · per address-familyInstanceIPv6-only · no IPv4R53 · DNS64synth AAAAInternet GW::/0 in + outEgress-only GW::/0 outboundNAT gatewayNAT64inbound + outbound — public by defaultoutbound only · no translation64:ff9b::/96
Figure 3 · Addressing: one /56, a /64 per subnet, every address a GUA. Nothing about the address makes it private — only the route and the security group do.
Your IPv4 assumptionThe 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.

::/0 → internet gateway is public-by-defaultthe instance is reachable from the internet the moment it boots — no NAT to hide behindInternetIPv6 + IPv4VPC · 2001:db8::/56Dual-stack LBedge · v4+v6 inPublic subnet /64 · ::/0 → IGWPrivate-egress /64 · ::/0 → EIGWIPv6-only /64 · ::/0→EIGW + NAT64SG · per address-familyInstanceGUA · reachable at bootSG · per address-familyInstanceoutbound onlySG · per address-familyInstanceIPv6-only · no IPv4R53 · DNS64synth AAAAInternet GW::/0 in + outEgress-only GW::/0 outboundNAT gatewayNAT64inbound + outbound — public by defaultoutbound only · no translation64:ff9b::/96
Figure 4 · ::/0 → internet gateway is public-by-default — inbound and outbound, reachable at boot. The only thing standing between the instance and the internet is the security group.

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.

::/0 → egress-only IGW is outbound-onlythe IPv6 stand-in for the NAT gateway — but it does no address translationInternetIPv6 + IPv4VPC · 2001:db8::/56Dual-stack LBedge · v4+v6 inPublic subnet /64 · ::/0 → IGWPrivate-egress /64 · ::/0 → EIGWIPv6-only /64 · ::/0→EIGW + NAT64SG · per address-familyInstanceGUA · reachable at bootSG · per address-familyInstanceoutbound onlySG · per address-familyInstanceIPv6-only · no IPv4R53 · DNS64synth AAAAInternet GW::/0 in + outEgress-only GW::/0 outboundNAT gatewayNAT64inbound + outbound — public by defaultoutbound only · no translation64:ff9b::/96
Figure 5 · ::/0 → egress-only internet gateway is outbound-only — the IPv6 replacement for the NAT gateway’s ‘let private instances out’ job, but it translates nothing; the instance keeps its GUA.

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.

IPv6-only reaches IPv4 with DNS64 + NAT64DNS64 synthesizes AAAA from 64:ff9b::/96; NAT64 (auto on the NAT gateway) translatesInternetIPv6 + IPv4VPC · 2001:db8::/56Dual-stack LBedge · v4+v6 inPublic subnet /64 · ::/0 → IGWPrivate-egress /64 · ::/0 → EIGWIPv6-only /64 · ::/0→EIGW + NAT64SG · per address-familyInstanceGUA · reachable at bootSG · per address-familyInstanceoutbound onlySG · per address-familyInstanceIPv6-only · no IPv4R53 · DNS64synth AAAAInternet GW::/0 in + outEgress-only GW::/0 outboundNAT gatewayNAT64inbound + outbound — public by defaultoutbound only · no translation64:ff9b::/96
Figure 6 · IPv6-only reaches IPv4: DNS64 synthesizes an AAAA from 64:ff9b::/96, and the route 64:ff9b::/96 → NAT gateway (NAT64, automatic) translates on the way out — one shared IPv4 for the subnet.
IPv6-only subnet route table. ::/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.

Security groups are per-address-familyyour IPv4 rules do nothing for IPv6 — write the ::/0 rules again or fail open or closedInternetIPv6 + IPv4VPC · 2001:db8::/56Dual-stack LBedge · v4+v6 inPublic subnet /64 · ::/0 → IGWPrivate-egress /64 · ::/0 → EIGWIPv6-only /64 · ::/0→EIGW + NAT64SG · per address-familyInstanceGUA · reachable at bootSG · per address-familyInstanceoutbound onlySG · per address-familyInstanceIPv6-only · no IPv4R53 · DNS64synth AAAAInternet GW::/0 in + outEgress-only GW::/0 outboundNAT gatewayNAT64inbound + outbound — public by defaultoutbound only · no translation64:ff9b::/96
Figure 7 · Security groups are per-address-family. The IPv4 rules on an instance say nothing about IPv6; you must add the matching ::/0 rules or the service fails open or closed.
FailureCauseFix
Fail-closed — service silent over IPv6Added IPv6 addressing + routes, but no SG rule for ::/0Add the matching ::/0 ingress rules (both SG and NACL)
Fail-open — more exposed than intendedAdded a broad ::/0 allow to unblock it; IPv6 is public-by-defaultScope the ::/0 rule to the ports and sources you actually meant
Part 3Decisions and shipping

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-stackIPv6-only
Solves IPv4 cost / exhaustion?No — still holds public IPv4Yes
Reaches IPv4-only endpointsNativelyVia DNS64 + NAT64
Requires IPv6-ready softwareNoYes — every workload, AMI, agent
Best forMigrating safelyNew 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.

Non-disruptive order.
  1. Add an Amazon-provided /56 to the VPC (IPv4 keeps working).
  2. Add a /64 to each subnet; enable auto-assign where you want it.
  3. Write the IPv6 security-group and NACL rules first — before routing, so nothing is unintentionally open.
  4. Add routes: ::/0 → IGW (public) or → egress-only IGW (private-out); 64:ff9b::/96 → NAT gateway + DNS64 for IPv6-only.
  5. Add AAAA records; make the load balancers dual-stack.
  6. Only then flip subnets to IPv6-only where the win is worth it and the workloads are ready.

Where it bites

GotchaWhyFix
Instance unexpectedly reachable from the internet::/0 → IGW is public-by-defaultUse an egress-only IGW for outbound-only; scope the SG
Service silent over IPv6Forgot the IPv6 SG / NACL rulesRewrite every relevant rule for ::/0
IPv6-only workload can’t reach a dependencyAn IPv4-only endpoint with no NAT64 routeAdd 64:ff9b::/96 → NAT gateway + DNS64
“The egress-only gateway will translate addresses”It does not — direction onlyUse a NAT gateway (NAT64) for translation
Still paying for IPv4 after “adding IPv6”You dual-stacked but never removed public IPv4Concentrate 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

  1. AWS — New: AWS Public IPv4 Address Charge + Public IP Insights (Feb 2024): aws.amazon.com/blogs/aws/new-aws-public-ipv4-address-charge
  2. 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
  3. 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
  4. AWS — Amazon ECS announces IPv6-only support (Sep 2025): aws.amazon.com/blogs/containers/amazon-ecs-announces-ipv6-only-support