NAT & IP Forwarding

Network Address Translation (NAT) lets a Linux machine act as a router, sharing a single public IP with an entire private network or redirecting traffic to internal servers. This guide covers enabling IP forwarding, SNAT/MASQUERADE, DNAT for port forwarding, REDIRECT for local proxying, building a complete Linux router, and the equivalent nftables syntax.

Back to the Networking hub. Related guides: Firewall Guide | Bonding & VLANs.

Enabling IP forwarding

The kernel must be told to forward packets between interfaces. By default this is disabled.

# Enable at runtime
echo 1 > /proc/sys/net/ipv4/ip_forward

# Or use sysctl
sysctl -w net.ipv4.ip_forward=1

# Persist across reboots
echo "net.ipv4.ip_forward = 1" >> /etc/sysctl.d/99-forwarding.conf
sysctl -p /etc/sysctl.d/99-forwarding.conf

# For IPv6 forwarding
sysctl -w net.ipv6.conf.all.forwarding=1

Without forwarding enabled, the kernel drops any packet that arrives on one interface and is destined for another.

SNAT and MASQUERADE

Source NAT rewrites the source address of outgoing packets so that replies come back to the router rather than to the internal host directly.

# MASQUERADE: automatically uses the outgoing interface's address.
# Best for interfaces with dynamic (DHCP) addresses.
iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE

# SNAT: explicitly sets the source address.
# Slightly more efficient; use when the external IP is static.
iptables -t nat -A POSTROUTING -o eth0 -j SNAT --to-source 203.0.113.5

# Restrict NAT to a specific source subnet
iptables -t nat -A POSTROUTING -s 10.0.0.0/24 -o eth0 -j MASQUERADE

Both require a corresponding FORWARD chain that allows the traffic:

# Allow forwarding for NAT-ed traffic
iptables -A FORWARD -i eth1 -o eth0 -j ACCEPT
iptables -A FORWARD -i eth0 -o eth1 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

DNAT (Destination NAT / Port Forwarding)

DNAT rewrites the destination address in PREROUTING, redirecting incoming connections to an internal server.

# Forward external port 8080 to internal host 10.0.0.50:80
iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 8080     -j DNAT --to-destination 10.0.0.50:80

# Forward a range of ports
iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 3000:3100     -j DNAT --to-destination 10.0.0.50

# Forward UDP (e.g. VPN)
iptables -t nat -A PREROUTING -i eth0 -p udp --dport 1194     -j DNAT --to-destination 10.0.0.60:1194

You must also allow the forwarded traffic:

iptables -A FORWARD -i eth0 -d 10.0.0.50 -p tcp --dport 80 -j ACCEPT

REDIRECT

REDIRECT sends packets to the local machine on a different port. It is commonly used for transparent proxying.

# Redirect all outbound HTTP to a local proxy on port 3128
iptables -t nat -A PREROUTING -i eth1 -p tcp --dport 80     -j REDIRECT --to-port 3128

# Redirect DNS to a local resolver
iptables -t nat -A PREROUTING -i eth1 -p udp --dport 53     -j REDIRECT --to-port 5353

Building a Linux router

A complete example turning a two-NIC machine into a NAT router:

#!/bin/bash
# eth0 = WAN (DHCP from ISP), eth1 = LAN (10.0.0.1/24)

# 1. Enable forwarding
sysctl -w net.ipv4.ip_forward=1

# 2. Flush existing rules
iptables -F
iptables -t nat -F

# 3. Default policies
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT ACCEPT

# 4. Allow loopback and established traffic
iptables -A INPUT -i lo -j ACCEPT
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

# 5. Allow SSH from LAN
iptables -A INPUT -i eth1 -p tcp --dport 22 -j ACCEPT

# 6. Allow DHCP on LAN interface
iptables -A INPUT -i eth1 -p udp --dport 67 -j ACCEPT

# 7. NAT: masquerade LAN traffic going out on WAN
iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE

# 8. Allow forwarding from LAN to WAN and return traffic
iptables -A FORWARD -i eth1 -o eth0 -j ACCEPT
iptables -A FORWARD -i eth0 -o eth1 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

# 9. Port forward: external port 443 -> internal web server
iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 443     -j DNAT --to-destination 10.0.0.50:443
iptables -A FORWARD -i eth0 -d 10.0.0.50 -p tcp --dport 443 -j ACCEPT

# 10. Save
netfilter-persistent save

nftables NAT

The same NAT operations expressed in nftables:

# Create a nat table
nft add table ip nat
nft add chain ip nat prerouting { type nat hook prerouting priority -100 \; }
nft add chain ip nat postrouting { type nat hook postrouting priority 100 \; }

# MASQUERADE
nft add rule ip nat postrouting oifname "eth0" masquerade

# SNAT
nft add rule ip nat postrouting oifname "eth0" snat to 203.0.113.5

# DNAT: port forward 8080 -> 10.0.0.50:80
nft add rule ip nat prerouting iifname "eth0" tcp dport 8080 dnat to 10.0.0.50:80

# REDIRECT: transparent proxy
nft add rule ip nat prerouting iifname "eth1" tcp dport 80 redirect to :3128

# List the nat table
nft list table ip nat

Verifying NAT

# Watch the NAT conntrack table in real time
conntrack -L -n
conntrack -E     # event mode: shows new connections as they happen

# Check the MASQUERADE counter
iptables -t nat -L -n -v

# Verify forwarding is enabled
cat /proc/sys/net/ipv4/ip_forward