When turning an Ubuntu Server machine into a network router, local gateway, container host, or virtualization hypervisor, internal clients connected to secondary network interfaces (or internal bridge devices) require access to upstream external networks. Traditionally, establishing Network Address Translation (NAT) and IP masquerading on Linux required configuring low-level iptables or nftables firewall chains manually, saving rules with iptables-persistent, and writing custom shell scripts to ensure rules re-applied across server reboots.
However, modern Ubuntu systems running systemd-networkd can manage network routing, packet forwarding, and IP masquerading declaratively without manual firewall scripts. By enabling IPForward=yes and IPMasquerade=both inside native .network configuration files, systemd-networkd automatically hooks into the Linux kernel’s netfilter subsystem. It creates and maintains ephemeral masquerading chains dynamically as network interfaces link up and down. This guide explains how declarative NAT operates in systemd-networkd and details how to configure an Ubuntu Server as a resilient NAT router and internet gateway.
Architecture of Declarative NAT and IP Masquerading in systemd-networkd
In a standard two-interface Linux router topology, the server has two distinct network boundaries:
- Uplink Interface (WAN / External): The public-facing or corporate network interface (e.g.
eth0orenp1s0) that receives an IP address and default gateway from an upstream ISP or router. This interface performs IP masquerading. - Downlink Interface (LAN / Internal Bridge): The internal-facing interface (e.g.
eth1,enp2s0, or a virtual bridgebr0) that serves local workstation subnets, virtual machines, or container networks. - Kernel Packet Forwarding (
IPForward): Dictates whether the Linux kernel is permitted to forward IPv4 and IPv6 packets between different network interfaces (configured viasysctl net.ipv4.ip_forward=1or declared directly in networkd unit files). - Declarative Masquerading (
IPMasquerade): WhenIPMasquerade=bothoripv4is declared on the external uplink interface, systemd-networkd injects netfilter masquerade rules. As internal packets from the LAN traverse the WAN interface, their source private IP address (e.g.192.168.10.45) is rewritten to the WAN interface’s public IP, tracking connection states via Linux conntrack tables.
Managing NAT declaratively ensures configuration consistency: network interfaces, static IP addressing, DHCP server pools, and NAT rules are version-controlled together inside /etc/systemd/network/ without managing separate firewall scripts.
Prerequisites and systemd-networkd Activation
Ensure that systemd-networkd is active on your Ubuntu Server. While Ubuntu uses Netplan as its default frontend abstraction, Netplan can generate systemd-networkd backend configurations or you can manage native networkd unit files directly.
Verify that systemd-networkd and systemd-resolved are active:
sudo systemctl enable --now systemd-networkd
sudo systemctl enable --now systemd-resolved
Identify your physical network interface names using ip link:
ip link
In this scenario, we assume:
enp1s0: The external WAN uplink connected to the internet.enp2s0: The internal LAN downlink serving the private subnet (192.168.100.0/24).
Configuring the Internal LAN Interface (Downlink)
Create a .network configuration file for the internal interface inside /etc/systemd/network/:
sudo nano /etc/systemd/network/10-internal.network
Define static IP addressing and enable systemd-networkd’s integrated DHCP server to automatically assign IP addresses to local client machines:
[Match]
Name=enp2s0
[Network]
# Static gateway IP for internal clients
Address=192.168.100.1/24
# Enable IP forwarding across this interface
IPForward=yes
# Enable integrated DHCP server for LAN clients
DHCPServer=yes
[DHCPServer]
PoolOffset=100
PoolSize=100
EmitDNS=yes
DNS=192.168.100.1
EmitRouter=yes
Save and close the file. The configuration establishes 192.168.100.1 as the default gateway for local clients and configures an automatic DHCP pool distributing addresses from 192.168.100.100 to 192.168.100.199.
Configuring the External WAN Interface with IPMasquerade (Uplink)
Next, configure the external uplink interface to perform declarative NAT masquerading for all outbound packets.
Create the external network unit file:
sudo nano /etc/systemd/network/20-external.network
Define WAN connectivity and enable IP masquerading:
[Match]
Name=enp1s0
[Network]
# Receive dynamic WAN IP and default gateway from upstream router/ISP
DHCP=yes
# Permit kernel packet forwarding
IPForward=yes
# Automatically inject netfilter NAT masquerade rules for outbound traffic
IPMasquerade=both
Key configuration directives include:
IPForward=yes: Instructs systemd-networkd to enable IPv4 and IPv6 kernel packet forwarding automatically across this interface.IPMasquerade=both: Rewrites source IP addresses for both IPv4 and IPv6 outbound traffic. (Alternatively, useIPMasquerade=ipv4if upstream IPv6 uses native routed prefixes without NAT).
Enabling Kernel IPv4 Forwarding in sysctl
While systemd-networkd manages per-interface forwarding flags, ensure system-wide IPv4 forwarding is permitted in the Linux kernel sysctl configuration:
sudo nano /etc/sysctl.d/99-ip-forward.conf
Add the following kernel parameters:
net.ipv4.ip_forward=1
net.ipv6.conf.all.forwarding=1
Apply the sysctl parameters immediately without rebooting:
sudo sysctl --system
Applying Configuration and Verifying Network State
Reload and restart systemd-networkd to parse the new network unit files and initialize the NAT masquerade chains:
sudo systemctl restart systemd-networkd
Verify that both interfaces are bound and healthy using networkctl:
networkctl status
The output confirms operational states:
● 2: enp1s0
Link File: /usr/lib/systemd/network/99-default.link
Network File: /etc/systemd/network/20-external.network
State: routable (configured)
Driver: virtio_net
Type: ether
Address: 203.0.113.45
Gateway: 203.0.113.1
● 3: enp2s0
Link File: /usr/lib/systemd/network/99-default.link
Network File: /etc/systemd/network/10-internal.network
State: routable (configured)
Driver: virtio_net
Type: ether
Address: 192.168.100.1
Validating Active NAT Masquerading and Conntrack Tables
To confirm that systemd-networkd successfully injected kernel NAT rules, inspect the netfilter/iptables NAT table:
sudo iptables -t nat -L -v -n
Review the POSTROUTING chain output:
Chain POSTROUTING (policy ACCEPT 120 packets, 7200 bytes)
pkts bytes target prot opt in out source destination
1482 124K MASQUERADE all -- * enp1s0 0.0.0.0/0 0.0.0.0/0
The output explicitly confirms that all packets leaving via enp1s0 are processed by the MASQUERADE target.
To monitor active client translation connections in real time, inspect the Linux connection tracking subsystem using conntrack:
sudo apt install -y conntrack
sudo conntrack -L
Each active connection displays the original LAN client source (e.g. src=192.168.100.105) alongside the rewritten masqueraded WAN source (src=203.0.113.45), verifying complete end-to-end declarative NAT routing on Ubuntu Server.