My first point was that a bare line in interfaces.d/eth0.cfg without it having an associated stanza is improper configuration. A "pre-up" line is done in association with a device. (When bringing up a device, run this script before you finish bringing it up.) Otherwise, *when do you want that script to run* ? Something like: iface eth0 inet dhcp pre-up /usr/local/sbin/firewall $IFACE Tells us that "when you bring up eth0, run this script" Now, it may be that the way the actual /e/n/i parser runs, means that whenever it hits an include statement, it just continues its current parsing, so you could get this behavior with: iface eth0 inet dhcp source interfaces.d/eth0.cfg However, you're actually just relying on the "source interfaces.d/*" line. Which means that maybe it gets associated with a device you want, but as soon as anything changes in that file, it may not get run at the right time. Given the content of: https://pastebin.canonical.com/194078/ I think you're missing how pre-up and post-up are meant to be used. Specifically, you have lots of routes being added, but you aren't grouping the routes-being-added with the device-that-will-carry those routes. For example: auto ens3 iface ens3 inet static address 10.2.1.146/23 gateway 10.2.0.88 dns-nameservers 90.147.165.82 mtu 1500 ... auto ens8 iface ens8 inet static address 10.2.4.166/24 dns-nameservers 10.3.4.210 mtu 1500 post-up route add -net 10.3.0.0 netmask 255.255.254.0 gw 10.2.0.1 metric 0 || true pre-down route del -net 10.3.0.0 netmask 255.255.254.0 gw 10.2.0.1 metric 0 || true post-up route add -net 10.4.0.0 netmask 255.255.254.0 gw 10.2.0.1 metric 0 || true pre-down route del -net 10.4.0.0 netmask 255.255.254.0 gw 10.2.0.1 metric 0 || true post-up route add -net 10.3.4.0 netmask 255.255.255.0 gw 10.2.4.1 metric 0 || true pre-down route del -net 10.3.4.0 netmask 255.255.255.0 gw 10.2.4.1 metric 0 || true That specifically says "when I ifup ens3" run *no* scripts. But when I "if up ens8" add routes for 10.3/16 via 10.2.0.1 and 10.4 via 10.2.0.1 and 10.3.4.0 via 10.2.4.1. However, if you did: ifdown ens3 ifup ens8 You would end up fairly broken, because now you have routes that say "if you want to get to 10.3.0.0/16 you should talk to 10.2.0.1", but the most likely device they would have wanted to use to *talk* to 10.2.0.1 was ens3 which is currently down. More likely, what you actually want is: auto ens3 iface ens3 inet static address 10.2.1.146/23 gateway 10.2.0.88 dns-nameservers 90.147.165.82 mtu 1500 post-up route add -net 10.3.0.0 netmask 255.255.254.0 gw 10.2.0.1 metric 0 || true pre-down route del -net 10.3.0.0 netmask 255.255.254.0 gw 10.2.0.1 metric 0 || true post-up route add -net 10.4.0.0 netmask 255.255.254.0 gw 10.2.0.1 metric 0 || true pre-down route del -net 10.4.0.0 netmask 255.255.254.0 gw 10.2.0.1 metric 0 || true auto ens8 iface ens8 inet static address 10.2.4.166/24 dns-nameservers 10.3.4.210 mtu 1500 post-up route add -net 10.3.4.0 netmask 255.255.255.0 gw 10.2.4.1 metric 0 || true pre-down route del -net 10.3.4.0 netmask 255.255.255.0 gw 10.2.4.1 metric 0 || true (eg, when bringing up interface X, ensure to add routes that involve interface X) Note that /e/n/i doesn't care about indenting. The indenting is there because it is more obvious for us humans reading the file. We *could* implement support to treat an include without the included file itself being 'whole'. But that just leaves to people not understanding why making a change in file A broke some bit of configuration in file B. Instead, you should be writing coherent configuration that we can both parse easily, and that will be stable in the face of future changes.