Linux as a Layer-3 Router & NAT Gateway: IP Forwarding, SNAT Masquerading & DNAT
Created: 2026-11-08 · Last updated: 2026-11-08 · Nejat Hakan
Executive Summary: By default, every Linux installation acts strictly as an end-station: if a network interface receives a packet whose destination IP does not match one of the host's own local addresses, the kernel silently discards it. Enabling Kernel IP Forwarding transforms standard Debian servers into high-throughput Layer-3 Routers and NAT Gateways. This guide details kernel forwarding sysctls, strict Reverse Path Filtering (RFC 3704), stateful SNAT/Masquerading, DNAT port forwarding, and connection tracking (
conntrack) optimization.
-
Kernel Layer-3 Forwarding
Transform Debian into an enterprise router: decrement TTLs, handle ICMP unreachable generation, and bridge multiple subnets without appliances.
-
Anti-Spoofing (
rp_filter)
Enforce strict Reverse Path Filtering (RFC 3704) to drop IP-spoofed packets before they consume router processing cycles.
-
Stateful SNAT & DNAT
Translate private RFC 1918 addresses to WAN IPs via
nftablesmasquerading, and forward public ports securely to internal services. -
Conntrack Table Tuning
Scale kernel connection tracking limits (
nf_conntrack_max) and TCP timeout parameters to handle tens of thousands of concurrent routed flows.
1. The Kernel Routing Pipeline: End-Station vs. Router Mode
When an Ethernet frame arrives on eth1, the network card driver passes the struct sk_buff to ip_rcv(). The kernel inspects the destination IP address against its local addresses:
%%{init: {'flowchart': {'wrappingWidth': 600}}}%%
flowchart TB
A["Packet arrives on Ingress Interface (eth1)"] --> B["ip_rcv(): Verify Checksum & Header"]
B --> C{"Routing Decision:<br/>Is Destination IP local to this host?"}
C -->|"YES: Local IP"| D["Pass to Local Socket<br/>(INPUT Hook -> Application)"]
C -->|"NO: Remote IP"| E{"net.ipv4.ip_forward == 1?"}
E -->|"0 (Disabled)"| DROP["SILENT DROP<br/>(Standard Linux End-Station Behavior)"]
E -->|"1 (Enabled)"| F["Pass to FORWARD Hook<br/>1. Decrement TTL by 1<br/>2. Evaluate nftables filter rules<br/>3. Consult Routing Table for Egress Link"]
F --> G["POSTROUTING Hook<br/>Apply SNAT / Masquerade"]
G --> H["Transmit out Egress Interface (eth0)"]
2. Kernel Router Hardening (sysctl.d)
Routing packets demands strict kernel security to prevent your server from acting as an open transit reflector or being tricked by IP-spoofed traffic:
# File: /etc/sysctl.d/70-linux-router.conf
# 1. Enable Layer-3 Forwarding for IPv4 and IPv6
net.ipv4.ip_forward = 1
net.ipv6.conf.all.forwarding = 1
net.ipv6.conf.default.forwarding = 1
# 2. Strict Reverse Path Filtering (RFC 3704 / BCP 38)
# Drops packets whose source IP does not match the routing table return path
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.default.rp_filter = 1
# 3. Disable ICMP Redirects (Prevents Man-in-the-Middle Route Poisoning)
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.default.accept_redirects = 0
net.ipv4.conf.all.send_redirects = 0
net.ipv4.conf.default.send_redirects = 0
# 4. Ignore Bogus ICMP Broadcasts (Smurf Attack Mitigation)
net.ipv4.icmp_echo_ignore_broadcasts = 1
# 5. Prevent SYN Flood Exhaustion on High-Throughput Routing Nodes
net.ipv4.tcp_syncookies = 1
Apply immediately without reboot:
3. Demystifying NAT: SNAT vs. Masquerading vs. DNAT
Network Address Translation (NAT) operates in two distinct directions within Netfilter:
%%{init: {'flowchart': {'wrappingWidth': 600}}}%%
flowchart TB
subgraph SNAT_FLOW["Source NAT (Masquerading) - POSTROUTING Hook"]
S1["Internal Host: 10.10.20.11:49210"] --> S2["Router eth0 (WAN): 203.0.113.5"]
S2 --> S3["Outgoing Packet rewrites Source to 203.0.113.5:54120"]
end
subgraph DNAT_FLOW["Destination NAT (Port Forwarding) - PREROUTING Hook"]
D1["Internet Client requests 203.0.113.5:443"] --> D2["Router PREROUTING Hook"]
D2 --> D3["Packet rewrites Destination to Internal Host 10.10.20.15:443"]
end
- SNAT (Source NAT): Modifies the source IP of packets leaving the private network. Used when the WAN interface has a static public IP.
- Masquerading: A specialized form of SNAT designed for interfaces with dynamic DHCP addresses. The kernel queries the interface IP dynamically for every connection.
- DNAT (Destination NAT / Port Forwarding): Modifies the destination IP before routing decisions occur (
PREROUTING). Directs external internet traffic to internal servers.
4. Multi-Interface Router Architecture
Consider a standard dual-NIC enterprise router deployment:
* eth0 (WAN): Public or upstream subnet (203.0.113.5/24, Gateway: 203.0.113.1).
* eth1 (LAN): Private internal subnet (10.10.20.1/24, serving workstations and servers).
# File: /etc/systemd/network/10-wan-eth0.network
[Match]
Name=eth0
[Network]
Address=203.0.113.5/24
Gateway=203.0.113.1
DNS=9.9.9.9
5. Complete Production nftables Router Configuration
Here is a hardened /etc/nftables.conf designed for a production Debian router. It enforces stateful filtering on routed traffic, masquerades outbound traffic, and forwards HTTPS traffic to an internal reverse proxy:
#!/usr/sbin/nft -f
# File: /etc/nftables.conf
flush ruleset
table inet router {
# 1. Destination NAT (Port Forwarding)
chain prerouting {
type nat hook prerouting priority dstnat; policy accept;
# Forward public HTTPS (443) on WAN (eth0) to internal reverse proxy (10.10.20.15:443)
iifname "eth0" tcp dport 443 dnat ip to 10.10.20.15:443
}
# 2. Host Firewall: Protects the Router Itself
chain input {
type filter hook input priority filter; policy drop;
# Accept established sessions and loopback
ct state established,related accept
ct state invalid drop
iifname "lo" accept
# Permit ICMP and ICMPv6
ip protocol icmp accept
ip6 nexthdr icmpv6 accept
# Allow SSH administration ONLY from the internal LAN (eth1)
iifname "eth1" tcp dport 22 accept
}
# 3. Transit Firewall: Controls Routed Traffic between LAN and WAN
chain forward {
type filter hook forward priority filter; policy drop;
# Accept established and related return traffic
ct state established,related accept
ct state invalid drop
# Permit outbound traffic from internal LAN (eth1) to the public Internet (eth0)
iifname "eth1" oifname "eth0" accept
# Permit incoming DNAT traffic to internal reverse proxy
iifname "eth0" oifname "eth1" ip daddr 10.10.20.15 tcp dport 443 accept
}
# 4. Source NAT (Masquerading)
chain postrouting {
type nat hook postrouting priority srcnat; policy accept;
# Masquerade private LAN traffic leaving through the WAN interface
oifname "eth0" ip saddr 10.10.20.0/24 masquerade
}
}
Apply and activate:
sudo nft -c -f /etc/nftables.conf && sudo nft -f /etc/nftables.conf
sudo systemctl enable --now nftables
6. High-Throughput Connection Tracking (conntrack) Tuning
When a Linux router handles high connection rates (e.g., thousands of clients or heavy peer-to-peer traffic), the connection tracking table can fill up, causing the kernel to drop all new connections (nf_conntrack: table full, dropping packet):
Calculating Conntrack Table Limits
# File: /etc/sysctl.d/80-conntrack-tuning.conf
# Maximum number of tracked connections (each consumes ~320 bytes of kernel RAM)
# 262144 connections * 320 bytes ~= 84 MB RAM
net.netfilter.nf_conntrack_max = 262144
# Reduce idle TCP timeout from default 5 days (432000s) to 1 hour (3600s)
net.netfilter.nf_conntrack_tcp_timeout_established = 3600
# Faster cleanup of closing connections
net.netfilter.nf_conntrack_tcp_timeout_close_wait = 60
net.netfilter.nf_conntrack_tcp_timeout_fin_wait = 120
net.netfilter.nf_conntrack_tcp_timeout_time_wait = 120
Apply changes:
Inspecting Conntrack Table Usage
# Display active vs. maximum connection tracking count
sysctl net.netfilter.nf_conntrack_count net.netfilter.nf_conntrack_max
# Display real-time drop statistics
sudo conntrack -S
7. Next Steps
Now that your Debian router forwards packets across subnets and masquerades outbound traffic, learn how to steer traffic dynamically across multiple WAN links or VPNs:
-
Policy-Based Routing (PBR) & Multiple Routing Tables
Route traffic based on Source IP, firewall marks (
fwmark), and interface rules usingip ruleand custom routing tables.