The Need for Policy-Based Routing (PBR)
In standard network routing, a server decides where to send outgoing packets based purely on the destination IP address. It consults its routing table, finds the best match (usually the default gateway), and forwards the packet. However, in complex enterprise environments, destination-based routing is not always sufficient.
Suppose you have an Ubuntu server with two network interfaces connected to two different ISPs. You want web server traffic (port 80/443) to exit via ISP A, but you want database synchronization traffic (port 3306) to exit via ISP B. Or, you want traffic originating from a specific internal IP subnet to bypass the default gateway and use a dedicated VPN tunnel.
This is where Policy-Based Routing (PBR) comes in. PBR allows you to route traffic based on criteria other than the destination IP—such as the source IP address or the incoming interface. On modern Ubuntu servers, PBR is configured natively using Netplan and iproute2 routing tables.
Step 1: Understanding Multiple Routing Tables
To implement PBR, Linux utilizes multiple routing tables. Instead of just having one “main” routing table, you can create custom tables (numbered 1 through 252) and assign distinct default gateways to them.
You then create Routing Policy Rules (using the ip rule command) that dictate which traffic should consult which routing table.
Step 2: Designing the Netplan Configuration
Assume we have an Ubuntu server with two network interfaces:
eth0: Connected to the primary corporate network (Gateway: 192.168.1.1). This is the default route for all normal traffic.eth1: Connected to a specialized high-speed backup link (IP: 10.0.5.50, Gateway: 10.0.5.1). We want traffic originating from 10.0.5.50 to strictly use the 10.0.5.1 gateway, ensuring asymmetric routing does not drop our packets.
Step 3: Writing the Netplan YAML File
Open your primary Netplan configuration file (typically located in /etc/netplan/):
sudo nano /etc/netplan/01-netcfg.yaml
Configure the standard eth0 interface as usual. For eth1, we will define a custom routing table (Table 100) and explicitly map a routing policy rule to it.
network:
version: 2
ethernets:
eth0:
dhcp4: no
addresses:
- 192.168.1.100/24
routes:
- to: default
via: 192.168.1.1
eth1:
dhcp4: no
addresses:
- 10.0.5.50/24
routes:
# Define a default route inside custom Table 100
- to: default
via: 10.0.5.1
table: 100
routing-policy:
# Force traffic originating from this IP to consult Table 100
- from: 10.0.5.50
table: 100
# Force traffic arriving on this interface to reply via Table 100
- iif: eth1
table: 100
Step 4: Applying the Configuration
Netplan translates this YAML syntax directly into systemd-networkd configuration files or NetworkManager connections, depending on your renderer.
To apply the configuration and instantly enforce the policy routing, run:
sudo netplan apply
Step 5: Verifying the Policy Routing
To ensure that the Linux kernel has correctly ingested your policy rules, you must inspect both the routing tables and the routing policy database.
First, check the custom routing table (Table 100) to ensure the alternative default gateway is present:
ip route show table 100
You should see: default via 10.0.5.1 dev eth1.
Next, verify the routing policy database using the ip rule command:
ip rule show
The output will list the rules in order of priority. You should see entries resembling:
0: from all lookup local
32764: from all iif eth1 lookup 100
32765: from 10.0.5.50 lookup 100
32766: from all lookup main
32767: from all lookup default
This confirms that if a packet originates from 10.0.5.50, the kernel will hit rule 32765, stop evaluating, look inside Table 100, and immediately push the packet out to the 10.0.5.1 gateway.
Conclusion
Policy-Based Routing solves complex asymmetric routing and multi-homing challenges that standard destination-based routing cannot handle. By utilizing Netplan’s native routing-policy directives, Ubuntu administrators can permanently and declaratively configure advanced multi-gateway architectures without writing fragile ip route bash scripts that break upon reboot.