Reduce AWS NAT Gateway Costs with an EC2 NAT Instance
A practical guide to building a self-managed NAT instance with Amazon Linux 2023, iptables, and AWS Graviton for private subnet internet access.
Published · Republished on Medium

AWS NAT Gateway is managed, highly available within its Availability Zone, and removes Linux host maintenance. Its bill includes an hourly charge for each gateway plus data-processing charges, which can be disproportionate for a small, low-traffic lab.
A self-managed EC2 NAT instance can reduce that fixed cost and provide a controlled Linux forwarding point. It also introduces a single-instance failure domain, instance bandwidth and connection-tracking limits, patching work, firewall state, and manual recovery. This guide is therefore aimed at development, lab, and explicitly accepted non-critical environments—not as a default replacement for managed NAT in production.
Check cheaper architectural options first
Before operating a NAT instance, inspect where the traffic goes:
- Use an S3 or DynamoDB gateway endpoint when those services dominate traffic.
- Evaluate interface VPC endpoints for supported AWS APIs, including their hourly and data-processing costs.
- Keep a NAT device in the same Availability Zone as its private subnets to avoid unnecessary cross-AZ transfer and dependency.
- Use an egress-only internet gateway for outbound IPv6; IPv4 NAT does not solve IPv6 egress.
- Remove internet access entirely from workloads that only need private services.
Cost optimization starts by avoiding NAT traffic, not merely replacing the device.
Compare costs with your own numbers
Do not publish a universal savings percentage. Calculate both designs for the same Region and observation window:
text
NAT Gateway estimate = gateway-hours
+ processed-GB
+ applicable cross-AZ/data-transfer charges
NAT instance estimate = EC2 instance-hours
+ EBS
+ Elastic IPv4 charges
+ applicable data transfer
+ monitoring, backup, and operator timeUse the AWS Pricing Calculator and actual flow or billing data. A NAT instance can become more expensive after sizing for throughput, redundancy, and engineering ownership.
Architecture and failure boundary

The private subnet route table sends 0.0.0.0/0 to the NAT instance. The NAT instance sits in a public subnet whose route table sends 0.0.0.0/0 to an internet gateway. It has a public or Elastic IP and translates outbound IPv4 connections.
One NAT instance serves one failure domain. If it stops, becomes impaired, exhausts connection tracking, or loses its route, dependent subnets lose outbound connectivity. A second instance alone does not provide failover: automation must detect failure and replace the route target safely. For production-critical multi-AZ workloads, the managed per-AZ NAT Gateway pattern is usually the clearer design.
Tested example scope
The original lab used:
text
Operating system : Amazon Linux 2023
Instance type : t4g.small
Architecture : arm64 / AWS Graviton
Public interface : ens5 in this specific instance
Address : Elastic IPThese values are observations, not sizing recommendations. Interface names, baseline/burst bandwidth, packets per second, and tracked-connection allowances vary. Benchmark the chosen instance type with representative connection churn and packet sizes.
Step 1: Launch and restrict the instance
Launch a current Amazon Linux 2023 image into the public subnet. Use an instance profile for management and prefer AWS Systems Manager Session Manager over public SSH. The security group should accept forwarded traffic only from approved private subnet CIDRs and allow only required outbound protocols.
Do not assign broad inbound access from the internet. An Elastic IP is useful when external services allowlist one stable egress address, but it is also a billed and security-relevant resource.
Step 2: Disable source/destination checking
EC2 normally requires an instance to be the source or destination of traffic. A NAT instance forwards packets for other instances, so source/destination checking must be disabled:
bash
aws ec2 modify-instance-attribute \
--instance-id i-0123456789abcdef0 \
--no-source-dest-checkVerify the actual state instead of relying on the change request alone:
bash
aws ec2 describe-instances \
--instance-ids i-0123456789abcdef0 \
--query 'Reservations[0].Instances[0].SourceDestCheck'The expected value is false.
Step 3: Enable persistent IPv4 forwarding
bash
sudo install -m 0644 /dev/null /etc/sysctl.d/99-nat-instance.conf
printf '%s\n' 'net.ipv4.ip_forward=1' | sudo tee /etc/sysctl.d/99-nat-instance.conf
sudo sysctl -p /etc/sysctl.d/99-nat-instance.conf
sysctl net.ipv4.ip_forwardThe final value must be 1. Manage this file through image building or configuration management so replacement instances receive the same configuration.
Step 4: Configure and persist NAT rules
AWS currently documents iptables-services for an Amazon Linux NAT AMI. Discover the default-route interface rather than assuming ens5:
bash
PUBLIC_IF=$(ip -o route show default | awk '{print $5; exit}')
test -n "$PUBLIC_IF"
printf 'NAT egress interface: %s\n' "$PUBLIC_IF"Install the persistence service and apply a scoped forwarding policy:
bash
sudo dnf install -y iptables-services
sudo systemctl enable --now iptables
PRIVATE_CIDR='10.0.16.0/20'
PUBLIC_IF=$(ip -o route show default | awk '{print $5; exit}')
sudo iptables -t nat -A POSTROUTING \
-s "$PRIVATE_CIDR" -o "$PUBLIC_IF" -j MASQUERADE
sudo iptables -A FORWARD \
-m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
sudo iptables -A FORWARD \
-s "$PRIVATE_CIDR" -o "$PUBLIC_IF" -j ACCEPT
sudo service iptables saveDo not use iptables -F FORWARD blindly on an existing host; it deletes unrelated forwarding policy. Review iptables-save before and after the change and test persistence through a controlled reboot.
If the instance is also an inspection point, define whether forwarding fails open or closed when the inspection process fails. Adding Suricata or proxying changes throughput, latency, memory, and recovery requirements; it is not free visibility.
Step 5: Update only the intended private route table
bash
aws ec2 replace-route \
--route-table-id rtb-0123456789abcdef0 \
--destination-cidr-block 0.0.0.0/0 \
--instance-id i-0123456789abcdef0Use create-route instead when no default route exists. Confirm that the route table is associated with the intended private subnets and that the NAT instance's public subnet has a route to the internet gateway.
Record the previous route target before changing it. That value is the fastest rollback if validation fails.
Step 6: Validate the full path
From a private instance with no public IPv4 address:
bash
curl --fail --silent --show-error https://checkip.amazonaws.com
curl --fail --silent --show-error https://aws.amazon.com/ > /dev/nullThe first response should match the expected NAT instance public IP. Also validate DNS resolution, TLS, package repositories, and the real application destinations. A successful ping alone does not prove TCP, DNS, or application behavior.
On the NAT instance, inspect forwarding state:
bash
sudo iptables -t nat -L POSTROUTING -n -v
sudo iptables -L FORWARD -n -v
sudo conntrack -SReboot the NAT instance during a maintenance window and repeat the test to prove rule persistence.
Monitoring and capacity
Monitor both EC2 and Linux signals:
- Instance status checks, CPU, network bytes/packets, disk, memory, and process health.
- ENA counters such as
bw_in_allowance_exceeded,bw_out_allowance_exceeded,pps_allowance_exceeded, andconntrack_allowance_exceeded. conntrack_allowance_availablewhere supported, plus Linux conntrack utilization.- Forwarded packet drops, interface errors, latency, and application egress failures.
- Configuration drift, missing patches, route changes, and Elastic IP changes.
CloudWatch's ordinary minute-level network metrics can miss microbursts. AWS documents ENA driver metrics as the more direct evidence of allowance-related queueing and drops.
Availability and recovery
A practical recovery procedure should be executable without logging into the failed host:
- Build a patched, tested NAT AMI or launch template.
- Replace the instance in the same Availability Zone.
- Disable source/destination checking on the replacement.
- Attach or remap the intended Elastic IP if required.
- Validate forwarding and firewall persistence.
- Replace the private route target.
- Run end-to-end egress tests.
Route failover automation needs safeguards against false positives and split-brain changes. Test failure detection, route convergence, connection loss, and rollback. Existing NAT connections are stateful on the failed instance and will not migrate seamlessly.
Security boundary
A NAT instance hides private addresses and permits return traffic for initiated connections; it is not a complete egress firewall. Security groups, network ACLs, DNS controls, application proxies, and workload identity still matter.
Flow logs show network metadata but not encrypted payloads. Passive IDS can analyze observable packet and flow characteristics, but TLS limits application-layer visibility unless traffic is deliberately terminated or decrypted under an approved design.
Restrict administrative access, require IMDSv2, use an instance role with minimal permissions, encrypt storage, patch promptly, and forward system/firewall logs to protected storage. Because many workloads depend on this host, compromise has a wide egress and availability impact.
Rollback
If the instance is unstable or capacity is insufficient:
- Restore the private route to the previous NAT Gateway or known-good NAT instance.
- Confirm outbound connectivity from each affected subnet.
- Preserve instance and network evidence for troubleshooting.
- Stop or isolate the failed NAT instance only after traffic has moved.
Never delete the previous NAT path until the observation window and failure test have passed.
Decision checklist
Choose a NAT instance only when:
- The measured savings exceed instance, IPv4, storage, monitoring, and operator costs.
- Outbound downtime is acceptable or tested failover exists.
- Traffic stays within proven bandwidth, PPS, and connection limits.
- The team owns patching, image refresh, firewall state, monitoring, and recovery.
- The need for custom forwarding or inspection justifies the extra operational surface.
Prefer managed NAT Gateway when availability, scale, and lower operational responsibility are more important than small-environment savings.
Further reading
- AWS: NAT instances
- AWS: Work with NAT instances
- AWS: NAT Gateway pricing considerations
- AWS: EC2 instance network bandwidth
- AWS: Monitor ENA network performance
Final thoughts
An EC2 NAT instance remains a useful and sometimes economical pattern for small environments, but its savings are purchased with operational ownership. Measure the real traffic and full cost, keep each NAT path zonal, preserve a rollback route, and prove reboot, saturation, and failure recovery before depending on it.