Skip to content

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 nftables masquerading, 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:

sudo sysctl --system

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
# File: /etc/systemd/network/20-lan-eth1.network
[Match]
Name=eth1

[Network]
Address=10.10.20.1/24

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:

sudo sysctl --system

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 using ip rule and custom routing tables.

    Policy-Based Routing Guide