AWS VPC Explained (2026): Subnets, NAT Gateways, Endpoints and Costs

Oct 31, 2026 · 13 min · Ajay Kumar

When I go through an AWS bill with a team, the line that surprises people is rarely EC2. It is NAT Gateway, often next to a public IPv4 charge nobody remembers agreeing to. That is a networking problem wearing a cost costume, and fixing it starts with knowing what the VPC under the console wizard actually does. So this is AWS VPC explained the way I would draw it on a whiteboard in 2026: CIDRs and reserved addresses, route tables, internet and NAT gateways (including the new regional NAT), security groups versus NACLs, endpoints, peering versus Transit Gateway versus Cloud WAN, and the public IPv4 charge. Prices are us-east-1, from AWS's own price list in October 2026.

What is an AWS VPC?

A VPC (virtual private cloud) is a private, software-defined network that lives in one AWS Region and spans all of that Region's Availability Zones. You carve it into subnets, and each subnet sits entirely inside one Availability Zone. Everything else (instances, load balancers, Lambda ENIs, RDS, EKS nodes) gets an address from one of those subnets.

You create a VPC with an IPv4 CIDR between a /16 (65,536 addresses) and a /28 (16), the allowed range. AWS recommends RFC 1918 ranges and warns against 172.17.0.0/16, which AWS Cloud9 and SageMaker AI use. You can add secondary CIDRs (five IPv4 CIDRs per VPC by default, the primary included, adjustable to 50, per the quota page) but never resize one, so the first CIDR is the decision that is hard to undo. Pick a /16 that overlaps nothing you might ever peer with.

Every subnet also loses five addresses: the network address, .1 for the VPC router, .2 for the Amazon DNS resolver, .3 reserved for future use, and the broadcast address. A /24 gives you 251 usable IPs and a /28 gives you 11. On EKS, where pods take VPC IPs by default, that arithmetic decides whether pods schedule.

Every Region also ships with a default VPC: 172.31.0.0/16, a /20 public subnet in each AZ, an internet gateway, and a main route table that sends 0.0.0.0/0 to it. Instances there get a public IPv4 automatically: fine for a weekend project, wrong for anything holding data.

Public vs private subnets: it's the route table

There is no "public" checkbox on a subnet. AWS's own definition is about routing: a public subnet has a direct route to an internet gateway, a private subnet does not and needs a NAT device for outbound internet, and an isolated subnet has no routes outside the VPC at all.

Every route table starts with a local route for the VPC CIDR, so all subnets reach each other. A 0.0.0.0/0 route to an igw- target makes a subnet public; the same route to a nat- target makes it private with egress; no default route makes it isolated. The internet gateway is free and does one-to-one NAT only for instances with a public IPv4, so an instance in a public subnet without one still cannot reach the internet.

The trap is the main route table. Any subnet without an explicit association silently uses it, so one IGW route added to the main table can make a dozen "private" subnets public. Check before you trust a subnet's name:

# AWS CLI v2; JMESPath queries checked with the CLI 2.27 parser. All calls are read-only.
SUBNET=subnet-0123456789abcdef0
VPC=vpc-0123456789abcdef0

# Default route of the subnet's explicit route table: igw-... = public, nat-... = private
aws ec2 describe-route-tables \
  --filters Name=association.subnet-id,Values="$SUBNET" \
  --query "RouteTables[].Routes[?DestinationCidrBlock=='0.0.0.0/0'].[GatewayId,NatGatewayId]" \
  --output text

# Empty output means the subnet falls back to the main route table; inspect that
aws ec2 describe-route-tables \
  --filters Name=vpc-id,Values="$VPC" Name=association.main,Values=true \
  --query "RouteTables[].Routes[]" --output table

# Every network interface holding a public IPv4 (each one bills $0.005/hour)
aws ec2 describe-network-interfaces \
  --query "NetworkInterfaces[?Association.PublicIp].[NetworkInterfaceId,InterfaceType,Association.PublicIp]" \
  --output table

# Elastic IPs that are allocated but attached to nothing (still billed)
aws ec2 describe-addresses --query "Addresses[?AssociationId==null].PublicIp" --output text

How much does a NAT gateway cost in 2026?

A zonal NAT gateway in us-east-1 costs $0.045 per hour and $0.045 per GB processed, per the AWS price list and the worked example on the VPC pricing page. That is $32.85 a month before a single byte, with normal data transfer on top: $0.09 per GB out to the internet in the first tier, and $0.01 per GB in each direction when instance and NAT sit in different AZs, per the data transfer price list. Pulling 500 GB of model weights through a NAT costs $22.50 in processing alone, again for every node that pulls them.

Each zonal NAT lives in one AZ, scales from 5 to 100 Gbps, and supports 55,000 simultaneous connections per IP to each unique destination. AWS advises one NAT per AZ, because when a shared NAT's zone fails, every zone loses egress. So the classic three-AZ layout is $98.55 a month in NAT hours. The cheaper single NAT is a bet: two fewer NATs save $65.70 a month, which at $0.01 per GB in each direction covers about 3.3 TB a month of cross-AZ NAT traffic, and nothing covers the outage. I take that bet in dev and decline it in production; the outage postmortems of the last two years are argument enough.

On 19 November 2025 AWS launched regional NAT gateways. One NAT ID covers every AZ, it needs no public subnet, it expands into a new AZ when it sees an ENI there (which can take up to 60 minutes, with traffic crossing zones meanwhile), and it supports 32 IPs per AZ against 8 for zonal. It is billed at $0.045 per hour for each AZ it is active in, so three AZs cost the same $0.135 an hour as three zonal NATs. It buys simpler routing, not a smaller bill, and it does not do private NAT.

The cheaper alternatives are boring. A gateway endpoint takes S3 and DynamoDB traffic off the NAT for free, and because Amazon ECR stores image layers in S3, it also takes the bulk of every image pull off it. A NAT instance such as the MIT-licensed fck-nat (v1.4.0, February 2026) swaps the hourly fee for an instance you patch yourself. IPv6 egress, covered below, skips NAT entirely.

When I design networks for untrusted code (I build PandaStack, a Firecracker microVM cloud for AI agents, so this is my day job), I treat the NAT as plumbing, not policy. A NAT gateway cannot have a security group, and it keeps no connection log of its own: beyond aggregate CloudWatch metrics, flow logs are the only record of who talked to what.

Security groups vs NACLs: what's the difference?

Both filter traffic, at different layers and with different memory. The comparison in the VPC guide is short:

  • Security groups attach to network interfaces, contain allow rules only, evaluate every rule before deciding, and are stateful: if a request is allowed out, the reply is allowed back in.
  • Network ACLs attach to subnets, contain numbered allow and deny rules evaluated in order until one matches, and are stateless: return traffic must be allowed explicitly, which usually means opening ephemeral ports 1024 to 65535, the range NAT gateways use.

AWS calls security groups the primary mechanism and NACLs a coarse, secondary guard rail. The quotas push the same way: 60 inbound and 60 outbound rules per security group and 5 groups per interface by default, against 20 rules per direction on a NACL (40 maximum). Security groups can also reference other security groups, which is what makes them scale: "app tier may talk to database tier" survives every autoscaling event without anyone editing a CIDR. Inside Kubernetes the pod-level equivalent is a NetworkPolicy, covered in the NetworkPolicies guide. Keep NACLs for the rare explicit deny, and keep them few: a stateless rule list someone hand-tuned three years ago is where 2 a.m. connectivity tickets come from.

VPC endpoints: gateway vs interface

A VPC endpoint lets private subnets reach an AWS service without going through the internet or a NAT. There are two kinds, and they behave very differently.

Gateway endpoints exist only for S3 and DynamoDB. They cost nothing, and they work by adding a prefix-list route (pl-... to vpce-...) to the route tables you choose. Longest prefix match means S3 traffic in the same Region takes the endpoint even when a 0.0.0.0/0 route exists. The limits matter: they are Region-scoped, and resources in a peered VPC or on premises cannot use yours. That is the job interface endpoints do.

Interface endpoints (AWS PrivateLink) put an ENI in each subnet you pick, for most AWS services and third-party ones. They cost $0.01 per hour per AZ and $0.01 per GB for the first petabyte. Three AZs is $21.90 a month per service. Against NAT processing at $0.045 per GB, an interface endpoint saves $0.035 per GB, so it pays for itself at roughly 626 GB a month to that one service. Below that, leave the traffic on the NAT unless the reason is security rather than money.

Component What it does us-east-1 price (Oct 2026) Source
VPC, subnets, route tables The network and its routing No charge VPC pricing
Internet gateway Public IPv4 one-to-one NAT, IPv6 routing Free; data transfer applies IGW docs
Zonal NAT gateway IPv4 egress for private subnets, one AZ $0.045/hour + $0.045/GB VPC pricing
Regional NAT gateway One NAT that follows workloads across AZs $0.045 per AZ-hour + $0.045/GB VPC pricing
Public IPv4 address Any EIP or auto-assigned IP, in use or idle $0.005/hour Price list
Gateway endpoint (S3, DynamoDB) Prefix-list route to the service Free Gateway endpoints
Interface endpoint PrivateLink ENI per AZ $0.01 per AZ-hour + $0.01/GB Price list
VPC peering One-to-one VPC link No hourly fee; same-AZ free, cross-AZ $0.01/GB each way VPC pricing
Transit Gateway Regional hub for many VPCs $0.05 per attachment-hour + $0.02/GB Price list
Cloud WAN Policy-driven global network $0.50 per edge-hour, $0.065 per VPC attachment-hour, $0.02/GB Cloud WAN pricing
VPC Encryption Controls Monitor or enforce in-transit encryption $0.15 per non-empty VPC-hour Price list
What VPC pieces cost per month before any traffic (us-east-1, 730 hours, USD) VPC, IGW, gateway endpoints $0.00 1 public IPv4 address $3.65 Interface endpoint, 3 AZs $21.90 1 zonal NAT gateway $32.85 Transit Gateway, 1 VPC attachment $36.50 3 zonal NATs (one per AZ) $98.55 Regional NAT active in 3 AZs $98.55 VPC Encryption Controls, 1 VPC $109.50 Cloud WAN: 1 edge + 1 VPC $412.45 Per GB on top: NAT $0.045, interface endpoint $0.01, Transit Gateway and Cloud WAN $0.02, cross-AZ $0.01 each way. Internet egress $0.09/GB after 100 GB free. Sources: AWS Price List files (Oct 2026), VPC and Cloud WAN pricing pages.
Hourly prices from the AWS Price List API files for us-east-1 (published 16 October 2026) and the VPC and Cloud WAN pricing pages, multiplied by 730 hours. Scale: 1 unit per dollar.

VPC peering vs Transit Gateway vs Cloud WAN

VPC peering is a one-to-one link with no hourly fee. Data that stays inside one AZ has been free since May 2021, and cross-AZ traffic pays the normal $0.01 per GB each way. The limitations are the whole story: no overlapping CIDRs, no transitive routing (A to B and A to C does not give B to C), and no edge-to-edge routing, so a peer cannot borrow your internet gateway, NAT or gateway endpoint. Ten VPCs in a full mesh need 45 peerings, which is where peering stops being simple.

Transit Gateway is a regional hub. Every VPC attaches once at $0.05 an hour ($36.50 a month) and the sending side pays $0.02 per GB processed, per the pricing page. You get segmentable route tables, VPN and Direct Connect attachments, and inter-Region hub peering.

Cloud WAN is the managed global layer: one core network policy defines segments and Regions, and AWS builds a core network edge (essentially a managed Transit Gateway) in each Region. It is priced for organisations: $0.50 an hour per edge, $0.065 per VPC attachment-hour and $0.02 per GB, and AWS's own example of one edge, two VPCs and 100 GB comes to $461.90 a month.

My rule of thumb: peering for a handful of VPCs, Transit Gateway past five or six or once VPN and Direct Connect arrive, Cloud WAN only for several Regions with someone who owns the segmentation policy. Whichever you pick belongs in code, as the infrastructure-as-code state of play keeps showing.

Should you use IPv6 now that public IPv4 costs money?

Since 1 February 2024, AWS charges $0.005 per hour for every public IPv4 address, whether attached to an instance, a NAT gateway, a load balancer or nothing at all. That is $3.65 a month or $43.80 a year per address. AWS cited scarcity and an acquisition cost that had risen more than 300 percent in five years.

IPv6 in a VPC works differently. AWS assigns the VPC a /56 from its pool, subnets usually take /64s, and the addresses are globally unique, so they are public by default. Outbound-only access comes from an egress-only internet gateway, which is free and stateful, the IPv6 equivalent of a NAT without the NAT bill. Neither the VPC pricing page nor the VPC price list file has an hourly line for IPv6 addresses; you pay ordinary data transfer. For IPv6-only subnets that still need IPv4 destinations, NAT gateways do NAT64 with Route 53 Resolver's DNS64.

The catch is that plenty of dependencies are still IPv4-only, so the practical path is dual-stack: IPv6 everywhere, the egress-only gateway for IPv6 egress, and a NAT for whatever cannot speak it yet. Two newer guard rails belong in the same plan. VPC Block Public Access (November 2024) can block internet gateway and egress-only internet gateway traffic for a whole Region of an account, bidirectionally or ingress-only, with per-VPC or per-subnet exclusions. VPC Encryption Controls left free preview and became paid on 1 March 2026, billed per non-empty VPC, at $0.15 an hour in us-east-1, so turn it on where an auditor asks for it rather than everywhere by reflex.

To build all of this as code, the companion guide on building a 3-AZ VPC with Terraform covers each NAT layout, the free S3 endpoint, flow logs and a connectivity test.

FAQ

What is the difference between a public and a private subnet in AWS?

A public subnet's route table has a route to an internet gateway; a private subnet's does not, and reaches the internet (if at all) through a NAT gateway. Instances in a public subnet still need a public IPv4 or IPv6 address to be reachable.

Do I need a NAT gateway in every Availability Zone?

For production, AWS recommends it, because a shared NAT takes every AZ's egress down with its own zone. Three per-AZ NATs cost $98.55 a month in us-east-1, a single NAT $32.85 plus cross-AZ transfer, and a regional NAT gives per-AZ behaviour with one ID at the per-AZ price.

Is an AWS internet gateway free?

Yes, as is the egress-only internet gateway. You pay for data transfer out ($0.09 per GB in the first us-east-1 tier, after 100 GB free each month) and $0.005 an hour for every public IPv4 address behind it.

How many IP addresses does AWS reserve in each subnet?

Five: the network address, the next three (VPC router, DNS, future use) and the broadcast address. A /24 has 251 usable addresses and a /28, the smallest subnet AWS allows, has 11.

This quarter I would do four things in every account I own. Run the two IPv4 queries above and release what is idle. Add S3 and DynamoDB gateway endpoints to every route table, because free is hard to argue with. Choose the NAT layout per environment on purpose (single in dev, per-AZ or regional in production) instead of inheriting a module default. And make every new VPC dual-stack, so the public IPv4 count only goes down from here.

Related: Cloud Outages 2025-2026: What 7 Official Postmortems Actually Teach, Kubernetes Cost Optimization in 2026: 8% CPU Use and What Fixes It and Terraform State in 2026: S3 Locking Replaced DynamoDB, What Else Changed.

I'm Ajay Kumar — I build and operate PandaStack, an open-source Firecracker microVM cloud for AI agents, and write about the infrastructure and tooling around it.

Need this kind of infrastructure work? See what I do or email hello@ajayk.sh.


Related