How NAT Actually Works
Introduction¶
NAT lets many private hosts share one public IP. It's so ubiquitous it feels like part of IP itself, but it's a rewrite layer with real limits and quirks — port exhaustion, hairpinning, protocol breakage. Understanding the connection- tracking table behind it explains all of them.
Concepts¶
Three operations:
- SNAT (Source NAT) — rewrite the source IP:port of outbound packets. Masquerade is SNAT to "whatever the outgoing interface's IP is" (for dynamic addresses). This is what your home router does.
- DNAT (Destination NAT) — rewrite the destination of inbound packets. Port
forwarding:
:80on the public IP →10.0.0.5:8080. - PAT (Port Address Translation) — many-to-one. Because multiple internal hosts share one external IP, the router also rewrites the port and keeps a mapping so replies can be un-NATed back to the right host.
It's stateful¶
The mapping lives in the connection tracking table (conntrack on Linux).
Each entry: original tuple (src/dst IP:port), reply tuple, protocol, state,
timeout. When a reply comes back, conntrack looks up the entry and reverses the
translation.
sudo conntrack -L # every tracked flow
sudo conntrack -L -p tcp --state ESTABLISHED | wc -l
cat /proc/sys/net/netfilter/nf_conntrack_count # current
cat /proc/sys/net/netfilter/nf_conntrack_max # limit
Hands-on¶
# Masquerade a private LAN out eth0 (nftables shown; iptables in comments)
nft add rule ip nat postrouting oif "eth0" ip saddr 10.0.0.0/24 masquerade
# iptables: -t nat -A POSTROUTING -o eth0 -s 10.0.0.0/24 -j MASQUERADE
# Port-forward public :443 to an internal host
nft add rule ip nat prerouting iif "eth0" tcp dport 443 dnat to 10.0.0.5:8443
# iptables: -t nat -A PREROUTING -i eth0 -p tcp --dport 443 -j DNAT --to 10.0.0.5:8443
# Don't forget the forward filter rule
nft add rule ip filter forward ip daddr 10.0.0.5 tcp dport 8443 ct state new,established accept
# Enable forwarding
sysctl -w net.ipv4.ip_forward=1
Hairpin / NAT loopback¶
An internal host tries to reach the public IP of a service that's also internal. The packet goes to the router, gets DNAT'd back inside — but the reply goes straight from the server to the client (same subnet), bypassing the router, so the client sees a reply from the wrong IP and drops it. Fix: also SNAT hairpinned traffic so the reply must go back through the router:
nft add rule ip nat postrouting ip saddr 10.0.0.0/24 ip daddr 10.0.0.5 tcp dport 8443 masquerade
Better: split-horizon DNS so internal clients get the internal IP directly.
Verification and troubleshooting¶
- Outbound works, some connections drop under load — conntrack table full.
dmesgshowsnf_conntrack: table full, dropping packet. Raisenf_conntrack_maxand lower timeouts (nf_conntrack_tcp_timeout_time_wait,..._established). - Port forward not working — you added the
natrule but not theforwardfilter rule, orip_forward=0, or the internal host's default gateway isn't the NAT box (so replies go elsewhere). - Port exhaustion behind one IP — ~64k ports per protocol per destination
tuple. A few thousand clients all hitting the same destination can exhaust it.
Add more public IPs (
SNAT --to a.b.c.1-a.b.c.4) or reduce connection churn. - FTP / SIP / PPTP / IPsec broken through NAT — these embed IP addresses in
the payload, which NAT doesn't rewrite. FTP needs the
nf_conntrack_ftp/nf_nat_ftphelper (or passive mode); SIP needs an ALG or a proper media relay; IPsec needs NAT-T (UDP 4500). - Internal users can't reach the public URL of an internal service — hairpin NAT (above) or split DNS.
- Return traffic goes to the wrong internal host — a conntrack entry
collided or was flushed mid-connection.
conntrack -E(events) to watch; usually a conntrack table that's too small or timeouts too short.
Related tools and reading¶
- On-site: Port Checker, IP Lookup, Subnet Calculator.
- Related posts: nftables for iptables users, The TCP three-way handshake.
Stuck on something this site can't fix?Reach out to Prabath directly on LinkedIn.More in Network Protocols
DHCP DORA: How a Host Gets Its Address
The four DHCP packets, the renewal timers, why relays exist, and the tcpdump filter to watch it happen.
October 24, 2026SMB Versions and Signing, Demystified
SMB1 is a security liability, SMB3 encrypts and signs. Here's how dialect negotiation works and how to tell which one a connection actually used.
October 24, 2026DNS over TLS and DNS over HTTPS
Plain DNS is unencrypted and unauthenticated. DoT and DoH fix the transport — here's how each works, and the trade-off between privacy and network visibility.
October 24, 2026