Routes, gateways and NAT
How traffic finds its way in and out.
Part 1: The problem
Sam moved QuickCart’s API servers into private subnets, exactly as planned. The next morning, a security update wouldn’t install. The command just sat there, printing nothing, until it gave up. The servers had no way out to the internet at all.
Sam added a NAT gateway and updates worked again. A month later the bill arrived, with a large line for “NAT gateway data processing”. Every night, the servers copied the day’s receipts to S3 — and every gigabyte of it went through the NAT gateway, which charges for each one.
Both of Sam’s problems are about routing: where traffic is allowed to go, and which way it takes to get there.
Part 2: The idea
Every subnet has a route table: a list of rules. Each rule says traffic for these addresses goes to this target.
| Destination | Target | Means |
|---|---|---|
10.0.0.0/16 | local | Anything inside the VPC: deliver it inside the VPC |
0.0.0.0/0 | an internet gateway | Everything else: out to the internet, both ways |
0.0.0.0/0 | a NAT gateway | Everything else: out through the NAT gateway, one way |
| S3’s prefix list | a gateway endpoint | Anything for S3: through the private endpoint |
The VPC lesson showed the one line that makes a subnet public. This lesson is about everything else in the table.
The local route
Every route table starts with a route for the VPC’s own range, with the target local. You can’t delete it or change its target. It’s why every server in a VPC can reach every other one, whatever else the table says. (Whether they’re allowed to talk is a job for security groups.)
The most specific route wins
A packet often matches more than one route. 0.0.0.0/0 matches every address. 10.0.0.0/16 matches only the VPC. The rule is: the most specific match wins — the route with the longest prefix, the biggest number after the slash.
| Packet going to | Matching routes | Chosen |
|---|---|---|
10.0.21.9 (the database) | 10.0.0.0/16 and 0.0.0.0/0 | 10.0.0.0/16 → local. /16 is more specific than /0 |
| An address on the internet | only 0.0.0.0/0 | 0.0.0.0/0 |
| An S3 address | S3’s prefix list and 0.0.0.0/0 | The prefix list. It names S3’s exact ranges |
This is why you can add a special route without breaking the general one.
Which table does a subnet use?
Each subnet is associated with exactly one route table. Every VPC comes with a main route table, and any subnet you don’t associate explicitly uses it.
| Table | What it’s for | Good practice |
|---|---|---|
| Main | The fallback for every subnet not given another table | Leave it with only local, so a new subnet starts isolated, not public by accident |
| Custom | One per kind of subnet: public, app, data | Associate every subnet on purpose |
The internet gateway: two-way, one per VPC
An internet gateway is the VPC’s door to the internet. There is one per VPC, it scales by itself, and it costs nothing. Traffic flows both ways through it — but only for servers that also have a public IP address. A server with only a private address can’t use it, even if the route is there.
The NAT gateway: one-way, out only
Private servers still need to reach out: to download updates, to call a payment provider. A NAT gateway gives them a way out without a way in.
It sits in a public subnet. Private subnets send 0.0.0.0/0 to it. It swaps each server’s private address for its own public one, sends the request on through the internet gateway, and passes the reply back. Nothing on the internet can start a connection to the servers behind it.
| NAT gateway fact | What it means for you |
|---|---|
| Needs an Elastic IP | A fixed public address that stays yours until you release it. The NAT gateway uses it as the address the internet sees |
| Lives in one zone | If that zone fails, the NAT gateway goes with it |
| Costs money every hour, plus every gigabyte | In us-east-1, about $0.045 an hour and about $0.045 per GB processed at the time of writing. Prices change; check the VPC pricing page |
| Can’t be stopped | Only deleted. It bills every hour it exists |
One per zone, or one for all? Production puts a NAT gateway in each zone, and each zone’s private subnets route to their own. Lose a zone, and the other zone still has its way out. A test environment often shares one NAT gateway to halve the cost, accepting that losing that zone cuts everyone off. AWS has also recently added a regional mode that spreads a NAT gateway across zones for you; this lesson uses the long-standing zonal kind, which is what you’ll meet in most existing setups.
local inside the VPC, an internet gateway for two-way, a NAT gateway for out-only, an endpoint for a private path to an AWS service.VPC endpoints: reaching AWS without the internet
S3, Secrets Manager, ECR and the other AWS services live outside your VPC, on public addresses. A VPC endpoint gives your subnets a private path to one of them instead.
| Gateway endpoint | Interface endpoint | |
|---|---|---|
| For | S3 and DynamoDB only | Most other services: Secrets Manager, ECR, CloudWatch, Systems Manager |
| How it works | Adds a route to your route tables: S3’s prefix list → the endpoint | Puts a network interface with a private address in your subnets |
| Cost | Free | About $0.01 an hour per zone, plus data, at the time of writing |
| Why use it | Cuts NAT data charges; S3 traffic never leaves AWS’s network | Private servers reach the service with no NAT gateway at all |
A prefix list is a named, AWS-maintained list of a service’s address ranges, shown as an ID starting pl-. You’ll see it as the destination of the endpoint’s route.
Part 3: See it
Below is one subnet with one API server, and its route table. It starts with only the local route. Add and remove routes, and send the three kinds of traffic each time. Watch the label on the subnet change.
| Destination | Target | Action |
|---|
local route. The server can talk to the rest of the VPC and nothing else. Add a route, then send some traffic.What to notice: nothing about the server changed — only the table. The same server is public, private or isolated depending on one route, and S3 traffic switches to the endpoint the moment its more specific route exists.
Part 4: Do it
Costs money — about $1.20 a day while the NAT gateway and its Elastic IP exist, in us-east-1 — delete them after. Finished within an hour, this lab costs around 10 cents.
You’ll build a public subnet with a NAT gateway, a private subnet that uses it, and an S3 gateway endpoint, then prove a private server can reach out. About 40 minutes. Region: N. Virginia (us-east-1).
Create the VPC and two subnets
In the AWS console: VPCYour VPCsCreate VPC
Field Enter Resources to create VPC only Name tag quickcart-vpcIPv4 CIDR block IPv4 CIDR manual input IPv4 CIDR 10.0.0.0/16IPv6 CIDR block No IPv6 CIDR block Tenancy Default Choose Create VPC. Then:
In the AWS console: VPCSubnetsCreate subnet
Choose
quickcart-vpc, and add two subnets:Subnet name Availability Zone IPv4 subnet CIDR block quickcart-public-aus-east-1a 10.0.1.0/24quickcart-app-aus-east-1a 10.0.11.0/24Choose Create subnet.
You should seeTwo subnets. Both use the main route table, which has only the
localroute: both are isolated for now.Make the public subnet public
In the AWS console: VPCInternet gatewaysCreate internet gateway
Field Enter Name tag quickcart-igwChoose Create internet gateway, then Actions → Attach to VPC, pick
quickcart-vpc, and choose Attach internet gateway.In the AWS console: VPCRoute tablesCreate route table
Field Enter Name quickcart-rt-publicVPC quickcart-vpcChoose Create route table. On Routes, choose Edit routes → Add route:
Destination Target 0.0.0.0/0Internet Gateway, then quickcart-igwChoose Save changes. On Subnet associations, choose Edit subnet associations, tick
quickcart-public-a, and choose Save associations.You should seequickcart-rt-publicwith two routes,10.0.0.0/16 → localand0.0.0.0/0 → igw-…, and one associated subnet.Create the NAT gateway in the public subnet
In the AWS console: VPCNAT gatewaysCreate NAT gateway
Field Enter Name quickcart-nat-aAvailability mode (if the form shows it) Zonal Subnet quickcart-public-a— the public oneConnectivity type Public Elastic IP allocation ID Choose Allocate Elastic IP Choose Create NAT gateway. The clock on its cost starts now.
You should seeState: Pending, changing to Available after a minute or two. The details show a Primary public IPv4 address: the Elastic IP the internet will see.
Route the private subnet through the NAT gateway
In the AWS console: VPCRoute tablesCreate route table
Field Enter Name quickcart-rt-appVPC quickcart-vpcChoose Create route table. On Routes, choose Edit routes → Add route:
Destination Target 0.0.0.0/0NAT Gateway, then quickcart-nat-aSave, then on Subnet associations associate
quickcart-app-a.You should seequickcart-rt-appwith10.0.0.0/16 → localand0.0.0.0/0 → nat-…. The app subnet is now private: out only.Add the S3 gateway endpoint
In the AWS console: VPCEndpointsCreate endpoint
Field Enter Name tag quickcart-s3Type (or Service category) AWS services Services Search s3, and choosecom.amazonaws.us-east-1.s3with type GatewayVPC quickcart-vpcRoute tables Tick quickcart-rt-appPolicy Full access Choose Create endpoint.
You should seeOpen
quickcart-rt-appagain. It now has a third route: destinationpl-…(S3’s prefix list), targetvpce-…. You never typed it — creating the endpoint added it. From now on S3 traffic from the app subnet skips the NAT gateway.Launch a test server in the private subnet
This server has no public address and no way to log in. It will check the internet as it boots and write the answer to its system log.
In the AWS console: EC2InstancesLaunch instances
Field Enter Name quickcart-route-testApplication and OS Images Amazon Linux 2023 — the default Instance type t3.micro Key pair Proceed without a key pair Network settings Choose Edit VPC quickcart-vpcSubnet quickcart-app-aAuto-assign public IP Disable Firewall (security groups) Create security group, and untick Allow SSH traffic from. Nothing needs to connect in Open Advanced details. In User data, paste this script:
user data#!/bin/bash if curl --silent --max-time 10 --output /dev/null https://example.com; then echo "QUICKCART CHECK: the internet is reachable" else echo "QUICKCART CHECK: the internet is NOT reachable" fiPart What it means #!/bin/bashrun this script with bash. User data runs once, as root, on first boot if … then … else … firun the first echoif the command succeeds, the second if it failscurlfetch a web address --silentdon’t print a progress bar --max-time 10give up after 10 seconds instead of hanging --output /dev/nullthrow the page away; only success or failure matters https://example.coma public test site; any public HTTPS address would do echo "QUICKCART CHECK: …"print a line that’s easy to spot in the log Choose Launch instance.
Read the answer in the system log
Wait until the instance shows Running and its status checks have passed — a few minutes.
In the AWS console: EC2Instancesquickcart-route-testActionsMonitor and troubleshootGet system log
Scroll near the end, or use your browser’s find, for
QUICKCART CHECK.Among the boot messagesQUICKCART CHECK: the internet is reachableThe log can take a few minutes to fill in; if the line isn’t there, wait and choose Get system log again. The lines around it will differ from anyone else’s.
You should seeA server with no public address reached the internet: out through
quickcart-nat-a, throughquickcart-igw, and back. Nothing on the internet can reach it.Clean-up step: Clean up
Do these in order. Each one frees what the next one needs.
1. Terminate the test server.
In the AWS console: EC2Instancesquickcart-route-testInstance stateTerminate (delete) instance
2. Delete the endpoint.
In the AWS console: VPCEndpointsquickcart-s3ActionsDelete VPC endpoints
Type
deleteand confirm.3. Delete the NAT gateway, and wait.
In the AWS console: VPCNAT gatewaysquickcart-nat-aActionsDelete NAT gateway
Type
deleteand confirm. Refresh until State is Deleted. It takes a few minutes.4. Release the Elastic IP. This is the step people forget.
In the AWS console: VPCElastic IPsActionsRelease Elastic IP addresses
Tick the address the NAT gateway used, then choose Release.
5. Delete the VPC.
In the AWS console: VPCYour VPCsquickcart-vpcActionsDelete VPC
Type
deleteand choose Delete. If it complains that something is still in use, the instance is still terminating; wait a minute and try again.You should seeNo NAT gateways except the deleted one, no Elastic IPs in the list, no running instance, and no
quickcart-vpc. Nothing is left billing.
Part 5: Check yourself
A subnet's route table has 10.0.0.0/16 → local and 0.0.0.0/0 → nat-gateway. What kind of subnet is it?
The same table also has a route for S3's prefix list to a gateway endpoint. Where does a request to S3 go?
Where must a NAT gateway be created?
QuickCart runs in two zones with one NAT gateway, in zone a. Zone a fails. What happens to servers in zone b's private subnet?
You're cleaning up. What order works?
Part 6: Cheat sheet
| Term | In one line |
|---|---|
| Route table | Destination → target rules for a subnet. One table per subnet |
local | The VPC’s own range. Always there, can’t be removed |
| Most specific wins | Of all matching routes, the one with the biggest number after the slash |
| Main route table | Used by any subnet not associated with another. Keep it isolated |
| Public subnet | 0.0.0.0/0 → igw. Servers also need a public IP |
| Private subnet | 0.0.0.0/0 → nat. Out only |
| Isolated subnet | No 0.0.0.0/0 route |
| Internet gateway | One per VPC, two-way, free |
| NAT gateway | In a public subnet, one zone, needs an Elastic IP, bills hourly and per GB |
| High availability | One NAT gateway per zone, each zone routing to its own |
| Gateway endpoint | S3 and DynamoDB. A route, free. Cuts NAT charges |
| Interface endpoint | Other services. A private address in your subnet, bills hourly |
| Clean-up order | NAT gateway → wait for Deleted → release Elastic IP → VPC |