Multiple Default Routes in Ubuntu 16.04 (and other Debian based systems)

The Situation:

Your workstation/server has both a network connection to the local network with internet access, and a public IP address (or a second, different LAN/WAN/DMZ) on a separate interface. You’d like to run some services, perhaps the Apache web server, and serve them via the public IP address you have directly on your NIC, but still use the LAN connection for everything else. Or maybe you just want to be able to ssh directly to your workstation from home, without forwarding a port on the main firewall. Whatever you want to serve on the public IP address, this is how you can do it.

Warning: If you have a public IP address on your workstation, be sure to apply the proper firewall and security measures! Your workstation will be directly accessible from the Internet! If your workstation is compromised, an attacker will have a vector to your internal LAN though it.

The Solution:

We use the iproute2 extensions built into Ubuntu (And Debian. Other distros use it as well. YMMV) in order to set up a separate routing table for the second interface. This makes sure that requests hitting the IP address of the interface are replied to via that address, rather then through the default route (aka: the LAN). If you don’t do this, incoming connections are never answered.

We do this via the /etc/network/interfaces file. It’s possible to set up multiple interfaces exactly the same, but that is left as an exercise for the reader.

TL;DR:

Here is an example interfaces file:

auto eth1
iface eth1 inet static 
        address 123.123.123.123 
        netmask 255.255.255.224 
        rtgateway 123.123.123.97 
        rttable 1 
        post-up ip route add $IF_NETMASK dev $IFACE src $IF_ADDRESS table $IF_RTTABLE 
        post-up ip route add default via $IF_RTGATEWAY table $IF_RTTABLE 
        post-up ip route add $IF_NETMASK dev $IFACE src $IF_ADDRESS 
        post-up ip rule add from $IF_ADDRESS table $IF_RTTABLE 
        post-down ip route flush table $IF_RTTABLE

This example makes use of variables set in the file itself to make it easier to change and improve readability. The ‘rttable’ variable must be incremented for each publicly routed interface you set up. If you want to make the ‘rttable’ variable more readable, you can define them in the /etc/iproute2/rt_tables file like this:

# 
# reserved values 
# 
255     local 
254     main 
253     default 
0       unspec 
# 
# local 
# Define your new/custom routing tables here:
1      ispA
2      ispB
3      ispC

Then you can set the ‘rttable’ variable in /etc/network/interfaces to ‘ispA’ instead of ‘1’. However, this is not necessary. It works just fine with numbers, and you can always put a comment in the interfaces file to document which interface is which, if you want to.

After you’ve set this up, and rebooted (or brought the interface up manually, this is Linux after all), you can view the new routing table and rule for the interface by running:

$ ip route show table 1 
default via 123.123.123.97 dev eth1
255.255.255.224 dev eth1  scope link  src 123.123.123.123 
$ ip rule show
0:      from all lookup local  
32765:  from 123.123.123.123 lookup 1  
32766:  from all lookup main  
32767:  from all lookup default

Here we see the routing table for eth1, as well as the rule that tells the system to use it for incoming connections to eth1’s IP address.