Back to posts
May 23, 2026
11 min read

NAT Instance vs NAT Gateway: Self-Managed or Let AWS Handle It?

You’re reviewing this month’s AWS bill. The NAT Gateway line item stands out — $0.045/hour plus $0.045/GB of data processed. With dev/staging environments running 24/7, NAT Gateway costs can reach hundreds of dollars per month just for EC2 instances in private subnets to download packages or call external APIs.

A colleague suggests: “Why not use a NAT Instance for dev? Run it on a t3.micro — costs just a few bucks.”

NAT Instance — sounds familiar but you’ve never set one up. It’s also NAT, but runs on EC2 instead of using a managed service. So how does it work? Why do you need to disable Source/Destination Check? And when should you use it instead of NAT Gateway?

This post assumes you already understand the basics of VPC, subnets, and route tables. If not, read AWS VPC: Understanding VPC Through a Packet’s Journey first.


1. What Is NAT? (Quick Recap)

In a VPC, resources in a private subnet (a subnet with no direct route to the internet) only have private IPs. The internet doesn’t know how to route to private IPs (which belong to the RFC 1918 range — IP addresses reserved for internal networks), so these resources cannot communicate directly with the internet.

NAT (Network Address Translation) solves this by placing a “proxy” in the public subnet: when an EC2 instance in a private subnet wants to reach the internet, the packet is sent to the NAT, which replaces the source IP with its own public IP and forwards the packet out. When the response comes back, the NAT looks up its mapping table and routes it back to the original EC2.

AWS provides 2 ways to do NAT:

This post focuses on NAT Instance — how it works internally, then compares it with NAT Gateway.


2. NAT Instance — How It Works

2.1. Under the Hood: An EC2 Acting as NAT

A NAT Instance is essentially an EC2 instance running an AMI (Amazon Machine Image) configured with IP forwarding (allowing the OS to forward packets between network interfaces instead of only processing packets destined for itself) and iptables (a Linux tool for managing firewall rules and NAT). When a packet arrives, instead of processing it at the application layer, the OS changes the source IP and forwards the packet out through another interface — just like a traditional NAT router.

To function, a NAT Instance requires 4 things:

  1. Placed in a public subnet — because it needs a route to the internet via the Internet Gateway (IGW)
  2. Elastic IP attached (EIP — a static public IP address provided by AWS) — so the internet can send responses back
  3. Source/Destination Check disabled — details in the next section
  4. Route table configured — the private subnet must have a route 0.0.0.0/0 → eni-xxx pointing to the NAT Instance’s ENI

AWS used to provide a pre-configured Amazon Linux AMI for NAT Instances, but this AMI reached the end of standard support on December 31, 2020. If you want to use a NAT Instance today, you have 2 options: find an alternative AMI on the AWS Marketplace (several community AMIs come pre-configured for NAT, such as fck-nat — an open-source alternative optimized for cost), or configure it yourself from a standard Linux AMI — enable IP forwarding in the kernel (net.ipv4.ip_forward = 1) and set up iptables NAT rules.

2.2. Source/Destination Check — Why Must It Be Disabled?

This is the most interesting part of understanding NAT Instance. Before understanding why it must be disabled, let’s understand why AWS enables it by default.

What Is Source/Destination Check?

By default, AWS enables Source/Destination Check on every EC2 instance. This feature checks: does the packet arriving at or leaving this instance have a source or destination IP that matches the instance’s own IP? If not — the packet is dropped immediately.

Why Does AWS Enable It by Default?

This is a defense in depth (multi-layer security) mechanism that works independently of Security Groups and NACLs — meaning even if your SG or NACL is misconfigured, this layer still prevents dangerous behavior:

1. Preventing IP SpoofingIP Spoofing is a technique where an attacker forges the source IP in a packet to deceive the target system. Attackers can use this in DDoS reflection attacks (spoofing the victim’s IP so servers send responses to the victim), or bypass firewalls by impersonating a trusted IP. Source/Dest Check ensures that outgoing packets must have the instance’s own IP as the source, preventing all forms of IP forgery.

2. Blocking Unauthorized Traffic Forwarding — Without this mechanism, a compromised EC2 instance could become a proxy to forward malicious traffic to other instances in the VPC, or perform lateral movement (a technique where an attacker moves from one compromised instance to other instances in the same network). By dropping all packets that don’t belong to the instance, AWS blocks this behavior at the infrastructure level.

3. Ensuring Routing Consistency — In a multi-tenant cloud environment, AWS needs to ensure traffic follows the defined route tables. If instances could freely forward traffic, any instance could “become a router” on its own and break the network topology, making troubleshooting and network auditing extremely difficult.

4. Secure by Default — AWS follows the Least Privilege principle: dangerous behaviors are blocked by default, and those who need them must explicitly opt in. Most workloads (web servers, app servers, databases) don’t need to forward traffic, so enabling Source/Dest Check by default makes sense.

PurposeProtects Against
Anti IP SpoofingIP forgery attacks, DDoS reflection
Block unauthorized forwardingCompromised instance as proxy, lateral movement
Ensure routing consistencyMisrouted traffic, audit difficulties
Secure by defaultAccidental vulnerabilities from misconfiguration

Why Must NAT Instance Disable It?

But NAT Instance is different — its entire job is to forward packets on behalf of others. Look at the packet it receives from an EC2 in a private subnet:

src: 10.0.2.20 ← IP of the EC2 in the private subnet dst: 3.5.6.7 ← IP of an external server

The NAT Instance has IP 10.0.1.30. Neither the source nor the destination matches the NAT Instance’s IP. With Source/Destination Check enabled, this packet gets dropped — the NAT Instance never even sees it.

Similarly, when the NAT Instance forwards a packet outbound, the original source IP (10.0.2.20) doesn’t match the NAT Instance’s IP → check fails → packet dropped again.

That’s why you must disable Source/Destination Check on the NAT Instance. Without this step, the NAT Instance is completely useless — it cannot forward a single packet. Besides NAT Instance, other use cases that require disabling this mechanism include: VPN instances, software firewalls, or any instance acting as a router.

With NAT Gateway, you don’t need to worry about this — AWS handles it automatically since it’s a managed service, not running on a regular EC2 instance.

2.3. Detailed Packet Flow

Let’s trace a packet from an EC2 in a private subnet to the internet via a NAT Instance:

EntityIP
EC2 (private subnet)10.0.2.20
NAT Instance (public subnet)Private: 10.0.1.30, EIP: 12.34.56.78
External server3.5.6.7

Outbound:

#LocationsrcdstAction
1EC210.0.2.203.5.6.7Create packet
2Route Table (private)10.0.2.203.5.6.7Match 0.0.0.0/0 → eni-xxx → forward to NAT Instance
3NAT Instance10.0.2.2010.0.1.303.5.6.7iptables replaces src IP, saves mapping in conntrack
4Route Table (public)10.0.1.303.5.6.7Match 0.0.0.0/0 → igw-xxx → forward to IGW
5IGW10.0.1.3012.34.56.783.5.6.71:1 NAT private → EIP
6Internet12.34.56.783.5.6.7Server receives request

Return:

#LocationsrcdstAction
1Server3.5.6.712.34.56.78Send response
2IGW3.5.6.712.34.56.7810.0.1.301:1 NAT EIP → private
3Route Table (public)3.5.6.710.0.1.30Match 10.0.0.0/16 → local
4NAT Instance3.5.6.710.0.1.3010.0.2.20Look up conntrack, replace dst IP
5Route Table (private)3.5.6.710.0.2.20Match 10.0.0.0/16 → local
6EC23.5.6.710.0.2.20Receive response

Just like NAT Gateway, there are 2 NAT translations on the outbound path:

2.4. Security Group for NAT Instance

Since a NAT Instance is an EC2, you can (and must) attach a Security Group (a virtual firewall at the instance level, stateful). This is both an advantage (granular traffic control) and a responsibility (misconfigure it and you lose connectivity).

Minimum Security Group rules for a NAT Instance:

Inbound Rules:

TypePortSourcePurpose
HTTP8010.0.2.0/24 (Private Subnet CIDR)Allow HTTP traffic from private subnet
HTTPS44310.0.2.0/24 (Private Subnet CIDR)Allow HTTPS traffic from private subnet
SSH22Your IPAdminister the NAT Instance (via IGW)

Outbound Rules:

TypePortDestinationPurpose
HTTP800.0.0.0/0Forward HTTP traffic to the internet
HTTPS4430.0.0.0/0Forward HTTPS traffic to the internet

For comparison: NAT Gateway does not support Security Groups — you can only control traffic using NACLs (Network Access Control Lists — a subnet-level firewall, stateless). With a NAT Instance, you get both layers of protection: Security Group (stateful, instance-level) + NACL (stateless, subnet-level).


3. NAT Gateway — Managed Service (Recap)

NAT Gateway is the option AWS recommends by default. Instead of managing your own EC2, you simply:

  1. Create a NAT Gateway in a public subnet
  2. Attach an Elastic IP
  3. Configure the route table: 0.0.0.0/0 → nat-xxx

AWS handles everything else: patching, monitoring, scaling, and high availability within the AZ. Bandwidth auto-scales from 5 Gbps up to 100 Gbps — no need to choose an instance type or worry about bottlenecks.

Note: EC2 instances in the same subnet as the NAT Gateway cannot use it — only EC2 instances from other subnets can route traffic through the NAT Gateway. This is why the NAT Gateway is always placed in a public subnet, while EC2 instances that need NAT reside in a private subnet.

For details on how NAT Gateway works (PAT, double NAT, connection tracking), see AWS VPC: Understanding VPC Through a Packet’s Journey — Flow 3: EC2 → Stripe.

Important note about High Availability: NAT Gateway has automatic HA within a single AZ. If that AZ goes down, the NAT Gateway goes down too. For region-wide HA, you need to deploy one NAT Gateway per AZ and configure route tables accordingly for each AZ’s private subnets. You don’t need to worry about cross-AZ failover — because when an AZ goes down, the EC2 instances in that AZ go down as well, so they no longer need NAT.


4. Detailed Comparison

CriteriaNAT InstanceNAT Gateway
NatureEC2 instance running a NAT-configured AMIFully managed AWS service
ManagementYou manage everything (patch, update, monitor)AWS manages everything
High AvailabilityNot built-in — must self-setup with ASG multi-AZAutomatic HA within 1 AZ
BandwidthDepends on instance typeAuto-scales 5–100 Gbps
PerformanceLimited by EC2 CPU/networkOptimized for NAT, high performance
CostPay for EC2 instance (cheaper with small instances)$0.045/hr + $0.045/GB (more expensive)
Security Group✅ Can attach❌ Not supported (NACL only)
Use as Bastion host✅ Possible❌ Not possible
Port forwarding✅ Supported❌ Not supported
Source/Destination CheckMust disable manuallyNot needed (AWS handles it)
Elastic IPCan use EIP or public IPEIP required

5. When Should You Use Which?

Use NAT Gateway when:

Use NAT Instance when:

SAA exam tip: exam questions often focus on the differences between NAT Instance and NAT Gateway. Remember 3 key concepts: Source/Destination Check (must be disabled for NAT Instance), Security Group (only NAT Instance supports it), and High Availability (NAT Gateway has it built-in per AZ, NAT Instance requires manual setup).


Conclusion

Back to the original question: your colleague suggested using a NAT Instance for the dev environment — this is a reasonable choice if you accept the trade-off of manual management and no built-in HA in exchange for lower costs.

Key takeaways:

Related