How to Configure Policy-Based Routing and RPDB in Ubuntu Using systemd-networkd

Standard Linux IP routing decisions evaluate only the destination IP address of an incoming or outgoing network packet, forwarding traffic across the default routing table according to longest prefix match. However, multi-homed Linux servers, virtualised edge gateways, and container hosts often require policy-based routing (PBR)—a routing architecture where packet forwarding decisions are based on source IP address, incoming network interface, Type of Service (ToS), or firewall packet marks. On Ubuntu systems governed by systemd-networkd, network administrators can define custom routing tables and Routing Policy Database (RPDB) rules natively within network unit files without writing custom ip rule shell scripts.

Understanding Policy-Based Routing and the Routing Policy Database (RPDB)

In the Linux kernel, the Routing Policy Database (RPDB) acts as an index of rules that dictates which routing table a packet must query. When the kernel processes a packet, it iterates sequentially through RPDB rules sorted by numerical priority (preference):

  • Priority 0 (local table): Reserved for loopback, broadcast, and locally hosted IP addresses.
  • User-Defined Rules: Custom matching rules that direct packets matching specific criteria (such as source subnet or firewall mark) to dedicated custom routing tables.
  • Priority 32766 (main table): The standard system routing table where standard interface routes and default gateways reside.
  • Priority 32767 (default table): Legacy fallback table, typically empty.

By declaring [RoutingPolicyRule] and [Route] blocks in systemd-networkd configuration files, routing policies persist reliably across reboots, interface state toggles, and dynamic network reconciliations.

Defining Custom Routing Table Identifiers

While the kernel supports numerical routing table IDs (1 to 255), assigning descriptive names to routing tables prevents configuration errors and simplifies auditing. Table mappings reside in the /etc/iproute2/rt_tables file.

To register custom table names, edit the configuration file with administrative privileges:

sudo tee -a /etc/iproute2/rt_tables << 'EOF'
# Custom Routing Tables for Multi-Homed Interfaces
100    dmz_traffic
200    vpn_traffic
EOF

Verify that the custom table entries are visible to the system IP utilities:

cat /etc/iproute2/rt_tables

Configuring Secondary Routes in systemd-networkd

Consider a dual-homed Ubuntu server with a primary management interface (eth0) and a secondary dedicated DMZ interface (eth1). To ensure packets arriving on eth1 exit exclusively via eth1‘s gateway rather than the default gateway on eth0, define a dedicated network configuration.

Create or update the network configuration file for the secondary interface (e.g. /etc/systemd/network/10-dmz.network):

sudo tee /etc/systemd/network/10-dmz.network << 'EOF'
[Match]
Name=eth1

[Network]
Address=192.168.10.50/24
DNS=1.1.1.1 8.8.8.8

# Add subnet and default route into the custom dmz_traffic table
[Route]
Table=100
Destination=192.168.10.0/24

[Route]
Table=100
Gateway=192.168.10.1
GatewayOnLink=true

# Direct traffic originating from eth1's IP to table 100
[RoutingPolicyRule]
From=192.168.10.50/32
Table=100
Priority=1000

# Direct incoming traffic arriving on eth1 to table 100
[RoutingPolicyRule]
IncomingInterface=eth1
Table=100
Priority=1010
EOF

Routing by Firewall Packet Marks (FWMARK)

In addition to source and destination IP matching, systemd-networkd supports policy routing based on Netfilter firewall marks assigned by nftables or iptables. This enables routing decisions based on application port, protocol, or process UID.

To route all marked packets to a dedicated VPN table, declare an FWMARK policy rule:

[RoutingPolicyRule]
FirewallMark=0x5
Table=200
Priority=2000

Traffic tagged with mark 0x5 in the Netfilter mangle table will immediately bypass the main routing table and follow the routes defined in table 200.

Applying and Validating Routing Policy Configuration

Apply the new network configuration without terminating active SSH sessions by reloading systemd-networkd:

sudo networkctl reload

To inspect the status of the configured interface:

networkctl status eth1

To audit active RPDB rules in the Linux kernel:

ip rule show

The command outputs the operational rule hierarchy, confirming your custom rules:

0:      from all lookup local
1000:   from 192.168.10.50 lookup dmz_traffic
1010:   from all iif eth1 lookup dmz_traffic
32766:  from all lookup main
32767:  from all lookup default

To inspect the routes populated inside the custom table:

ip route show table dmz_traffic

Simulating Packet Path Lookups Using ip route get

To verify that outbound traffic originates and traverses the intended interface and gateway without sending live test traffic, execute route resolution queries:

# Query route resolution for outbound connection bound to eth1's IP
ip route get 8.8.8.8 from 192.168.10.50

# Query standard outbound route resolution
ip route get 8.8.8.8

The output for the first command will confirm table dmz_traffic via 192.168.10.1 dev eth1, demonstrating that policy-based routing actively prevents asymmetric routing and dropped return packets.

Configuring policy-based routing with systemd-networkd provides Ubuntu infrastructure with enterprise-grade multi-homing resilience and deterministic packet delivery.

Get the best tech tips delivered straight to your inbox.

Join thousands of readers mastering Apple, Google, Microsoft, and Linux.