[AWS] Cutting NAT Gateway Costs — Building a NAT Instance with iptables (Part 2)
Overview
- Why a NAT Instance (instead of a NAT Gateway) — Part 1
- Using the AWS community NAT Instance AMI — Part 1
- Building a NAT Instance manually with iptables — Part 2 (this post)
In Part 1 we built a NAT Instance from the AWS community AMI. But that AMI runs an end-of-service, old OS that's hard to maintain. So in Part 2 we configure a NAT Instance directly on the latest AWS OS by manipulating iptables.
The network diagram was covered in Part 1, so I'll just recap the situation. For a Private Subnet to reach the internet, you need a NAT Gateway (translating Public IP ↔ Private IP — essentially a router's role). Instead of an AWS NAT Gateway, we build a NAT Instance ourselves to cut costs. Part 2 covers how to make a plain OS with no NAT setup act as a NAT.
Configuring the NAT Instance (EC2)
1. Launch a basic EC2
Figure 1. Launching the NAT Instance EC2
Use the LTS AMI provided directly by AWS (not the community one). I prefer Ubuntu, so I chose Ubuntu 24.04 LTS. The NAT Instance must of course sit in the Public Subnet; all other route table examples are in Part 1.
2. Manipulating iptables on the EC2 OS (NAT setup)
What is iptables? A powerful Linux firewall / packet-filtering tool that can manage network traffic, act as a firewall, and implement Network Address Translation (NAT). Here we use its NAT feature, not the firewall.
SSH into the NAT Instance you created, then follow these steps.
2-1. Enable IP forwarding
echo "net.ipv4.ip_forward = 1" | sudo tee -a /etc/sysctl.conf
sudo sysctl -p
A NAT Instance must route traffic elsewhere (not to itself), so it must allow packets to come in and go out.
net.ipv4.ip_forward = 1— enables IP forwarding./etc/sysctl.conf— applies the setting permanently.sudo sysctl -p— applies the setting immediately.
Figure 2. The /etc/sysctl.conf editor
Alternatively, opening sysctl.conf shows net.ipv4.ip_forward=1 disabled with a # as in Figure 2. You can just uncomment it and apply.
2-2. Add an iptables NAT rule
sudo iptables -t nat -A POSTROUTING -o {your-eth} -j MASQUERADE
This NAT rule translates the private network's IP to the NAT Instance's public IP. The options:
-t nat— add the rule to the NAT table.-A POSTROUTING— apply NAT after routing (just before departure).-o {eth}— apply to traffic leaving via your interface (e.g.,eth0,ens5— the public network interface).-j MASQUERADE— translate the source IP to the NAT Instance's public IP (private network → internet).
Find your interface name with ip addr (or ifconfig after installing net-tools).
ip addr
Figure 3. Checking the network interface name (e.g., ens5)
In the figure above, ens5 is the interface name.
2-3. Verify the result
sudo iptables -t nat -L -n -v
This command lists the NAT table applied to iptables.
-t nat— inspect the NAT table.-L— list all chains and rules.-n— show IPs instead of hostnames.-v— show verbose info (packet/byte counts, etc.).
Figure 4. Result after adding the iptables rule (top: before / bottom: after)
As in Figure 4, the MASQUERADE rule should be added to the POSTROUTING chain. Once confirmed, save it so the setting persists across reboots.
2-4. Persist the rules (survive reboots)
sudo apt-get update
sudo apt-get install -y iptables-persistent
iptables-persistent saves iptables rules and auto-loads them on boot. If it asks whether to save the current rules during install, answer yes to all.
sudo netfilter-persistent save
This saves the current rules to /etc/iptables/rules.v4 etc. Saved rules survive reboots.
sudo systemctl enable netfilter-persistent
Enable the service so the rules auto-load after a reboot.
That completes the OS-side NAT Instance setup. The rest (Private Subnet route table, disabling the EC2 source/destination check) is identical to Part 1.
3. Result — verifying Private Subnet internet connectivity
Figure 5. NAT Instance result — internet connectivity confirmed
After finishing the setup, running sudo apt-get update on the Private Subnet EC2 confirms it now has internet.
Wrap-up
In Part 2 we configured the NAT Instance directly on the OS instead of using a community AMI. The remaining route table and console settings are identical to Part 1. Because I needed one instance to serve all three roles — Reverse Proxy / NAT Instance / Bastion — I run it configured manually on a version-managed, up-to-date OS as shown above.
📦 Migrated from my own Korean blog (my own writing). Original: taehyuklee.tistory.com/28

Comments