IP Masquerade HOWTO: share an Internet connection with Linux NAT

Updated November 2026

IP masquerading lets many computers on a private network share one public IP address through a Linux machine. You need three things: IP forwarding switched on, a NAT rule that rewrites outgoing traffic to the router's address, and firewall rules that let the LAN out but nothing unwanted in. Add dnsmasq and the LAN machines configure themselves.

How masquerading works

PC 192.168.50.101 PC 192.168.50.102 Phone 192.168.50.103 Linux router eth1 192.168.50.1 eth0 203.0.113.5 Internet from 203.0.113.5
Every LAN device leaves the router with the router's public address. Connection tracking remembers who asked, so replies go back to the right device.

The router keeps a table of active connections. When a reply comes back to 203.0.113.5, it looks up which inside machine started that connection and sends the reply there. Machines on the Internet cannot start a connection to an inside machine unless you add a port forward.

The examples use these names; change them to yours (see ip -br addr):

Step 1: address the LAN side

Configure eth1 permanently with your network tool. With NetworkManager:

sudo nmcli connection add type ethernet con-name lan ifname eth1 \
     ipv4.method manual ipv4.addresses 192.168.50.1/24
sudo nmcli connection up lan

Do not set a gateway on the LAN side; the default route goes out through the WAN. For systemd-networkd and netplan versions, see the Ethernet HOWTO.

Step 2: switch on IP forwarding

Linux does not forward packets between interfaces by default (net.ipv4.ip_forward is 0). Turn it on and make it permanent:

echo 'net.ipv4.ip_forward = 1' | sudo tee /etc/sysctl.d/90-router.conf
sudo sysctl --system
sysctl net.ipv4.ip_forward

The last command should print net.ipv4.ip_forward = 1. Files in /etc/sysctl.d/ are applied at every boot.

IPv6. IPv6 is normally routed, not masqueraded: the ISP delegates a prefix and the router hands out addresses from it. If you set net.ipv6.conf.all.forwarding = 1, the kernel stops accepting router advertisements on interfaces whose accept_ra is 1. If the WAN gets its IPv6 address that way, also set net.ipv6.conf.eth0.accept_ra = 2.

Step 3: NAT and firewall with nftables

This file is a complete router rule set. Save it as /etc/nftables.conf (Debian) or include it from /etc/sysconfig/nftables.conf (Fedora, RHEL; there, stop and disable firewalld first, or use the firewalld version below instead). It replaces any other rules; on a machine running Docker or libvirt, see the note in the Firewall HOWTO about using your own table instead of flush ruleset.

#!/usr/sbin/nft -f
flush ruleset

define WAN = "eth0"
define LAN = "eth1"
define LAN_NET = 192.168.50.0/24

table inet filter {
    chain input {
        type filter hook input priority filter; policy drop;

        ct state established,related accept
        ct state invalid drop
        iifname "lo" accept
        meta l4proto { icmp, ipv6-icmp } accept

        # SSH from anywhere (narrow this later)
        tcp dport 22 accept

        # DNS and DHCP for the LAN only (dnsmasq)
        iifname $LAN udp dport { 53, 67 } accept
        iifname $LAN tcp dport 53 accept
    }

    chain forward {
        type filter hook forward priority filter; policy drop;

        # Fix MTU problems on PPPoE and tunnel uplinks
        tcp flags syn tcp option maxseg size set rt mtu

        ct state established,related accept
        ct state invalid drop

        # LAN may go out to the Internet
        iifname $LAN oifname $WAN accept

        # Allow connections that a DNAT rule below forwarded
        ct status dnat accept
    }
}

table ip nat {
    chain prerouting {
        type nat hook prerouting priority dstnat; policy accept;

        # Port forward: Internet port 8080 -> web server 192.168.50.10:80
        iifname $WAN tcp dport 8080 dnat to 192.168.50.10:80
    }

    chain postrouting {
        type nat hook postrouting priority srcnat; policy accept;

        oifname $WAN ip saddr $LAN_NET masquerade
    }
}

What each part does:

masquerade
Rewrites the source address of LAN packets leaving through the WAN to whatever address the WAN has right now. Use it when the WAN address comes from DHCP or PPP. With a fixed public address you can use snat to 203.0.113.5 instead.
forward chain
Decides what may cross the router. Here: anything from LAN to WAN, replies coming back, and connections that a port forward sent in. Everything else is dropped.
dnat to
A port forward. Delete the line if you do not need one; the ct status dnat accept rule then has nothing to match.
maxseg size set rt mtu
Lowers the TCP segment size on new connections to fit the route's MTU. Without it, some web sites hang on PPPoE links whose MTU is smaller than 1500.

Check and load it with a rollback timer if you work over SSH (see avoiding lock-out):

sudo nft -c -f /etc/nftables.conf
sudo nft -f /etc/nftables.conf
sudo systemctl enable nftables

Or: the same with firewalld

On Fedora and RHEL, keep firewalld and use its zones. The built-in external zone already has masquerading switched on; a policy allows traffic from the LAN zone to the Internet zone:

sudo firewall-cmd --permanent --zone=external --change-interface=eth0
sudo firewall-cmd --permanent --zone=internal --change-interface=eth1
sudo firewall-cmd --permanent --zone=internal --add-service=dhcp --add-service=dns
sudo firewall-cmd --permanent --new-policy lan-out
sudo firewall-cmd --permanent --policy lan-out --add-ingress-zone internal
sudo firewall-cmd --permanent --policy lan-out --add-egress-zone external
sudo firewall-cmd --permanent --policy lan-out --set-target ACCEPT
sudo firewall-cmd --reload
sudo firewall-cmd --zone=external --query-masquerade

If NetworkManager manages the interfaces, set the zone in the connection profile too, so it sticks: sudo nmcli connection modify <wan-profile> connection.zone external. A port forward in firewalld:

sudo firewall-cmd --permanent --zone=external \
     --add-forward-port=port=8080:proto=tcp:toport=80:toaddr=192.168.50.10
sudo firewall-cmd --reload

firewalld's masquerade and forward-port options are IPv4 only. Check sysctl net.ipv4.ip_forward as in step 2.

Step 4: DHCP and DNS with dnsmasq

dnsmasq is a small DHCP server and DNS forwarder. Install it (sudo apt install dnsmasq or sudo dnf install dnsmasq) and put these lines in /etc/dnsmasq.conf. In the file, options are written like the long command-line options without the leading --.

# Serve only the LAN
interface=eth1
bind-interfaces

# Hand out 192.168.50.100-200, leases last 12 hours
dhcp-range=192.168.50.100,192.168.50.200,12h

# Always give this machine the same address
dhcp-host=52:54:00:12:34:56,192.168.50.10

# Local domain name for LAN hosts
domain=lan
sudo systemctl enable --now dnsmasq
sudo systemctl status dnsmasq

dnsmasq tells clients to use the router itself as their default gateway and DNS server unless you set other values with dhcp-option. It forwards DNS queries to the servers in the router's own /etc/resolv.conf.

bind-interfaces makes dnsmasq listen only on the LAN address and loopback, instead of on all addresses. That matters on Ubuntu and other systems with systemd-resolved, whose local stub resolver already uses port 53 on 127.0.0.53.

The quick way: NetworkManager “shared”

For a laptop sharing Wi-Fi to a wired device, or a one-off setup, NetworkManager can do all of the above itself. Set the LAN profile's IPv4 method to shared:

sudo nmcli connection modify lan ipv4.method shared
sudo nmcli connection up lan

NetworkManager then turns on forwarding, adds NAT rules, and starts its own DHCP and DNS service on that interface. If the profile has no address, it uses 10.42.x.1/24. The DHCP and DNS part is a dnsmasq process that NetworkManager starts, so the dnsmasq program must be installed (on Debian and Ubuntu it is the recommended dnsmasq-base package). Do not combine this with your own dnsmasq service on the same interface.

Test it

  1. On a LAN client, renew the lease and check: ip -br addr shows an address in 192.168.50.100–200, and ip route shows default via 192.168.50.1.
  2. Ping the router: ping -c 3 192.168.50.1.
  3. Test DNS and the way out: curl -sI https://www.kernel.org | head -1.
  4. On the router, watch translated traffic leave: sudo tcpdump -ni eth0 icmp while the client pings an outside host. The source address is the router's WAN address.
  5. Look at the rule counters: sudo nft list ruleset (add counter to a rule to count its packets).

If clients get an address but cannot reach the Internet, check, in this order: ip_forward is 1, the WAN interface name in the rules is right (ip -br link), the forward chain allows LAN to WAN, and the router itself has Internet access.

Common mistakes

The classic 2002 IP Masquerade HOWTO, written for Linux 2.0 to 2.4, is kept by the LDP at tldp.org.

Sources

The nftables rule set on this page was test-loaded with nftables 1.1.3.