To route specific network application traffic through a WireGuard VPN on a binded interface, you must use policy-based routing with ip rule and ip route commands. This ensures that only selected packets are sent via the tunnel while other traffic uses the default gateway, maintaining connectivity during SSH sessions or when managing multiple interfaces.
Understanding Policy-Based Routing
Standard routing tables often struggle when an application needs to bypass the system's default gateway but still maintain access to local resources. WireGuard creates a virtual network interface, typically named wg0, which has its own subnet. To direct traffic from a specific process or source IP to this interface, Linux uses the iproute2 suite for policy routing. This method allows you to define rules based on the source address of the packet rather than just the destination, ensuring that your application’s traffic is correctly identified and routed through the VPN tunnel.
This approach is critical when you have a binded network interface, such as a secondary Ethernet port or a bonded link, where you might want to keep general internet traffic on one path while sending sensitive data through the VPN. Without explicit rules, the kernel may drop packets or send them out the wrong interface, leading to connection timeouts or security leaks.
Configuring the Routing Rules
The first step is to identify the source IP address of the network application or the user running it. If you are running a service under a specific user account, you can assign a unique private IP address to that user's namespace or simply use the source IP of the machine if it is distinct. For most setups, we will assume you want to route traffic from a specific source IP, for example, 192.168.1.100, through the WireGuard interface.
Create a new routing table entry by editing the /etc/iproute2/rt_tables file. Add a new line with a unique number and name, such as:
100 wireguard_table
Next, add a route to this new table that points to the WireGuard interface. Assuming your WireGuard peer is reachable at 10.0.0.1 and the interface is wg0, run the following command:
ip route add 10.0.0.0/24 dev wg0 src 10.0.0.2 table wireguard_table
You must also ensure that the default gateway for this table is not set unless you intend to route all traffic from that source through the VPN. Instead, you need to add a rule that tells the kernel to look up this table for packets originating from your specific source IP:
ip rule add from 192.168.1.100 table wireguard_table
If you need to allow local communication without the VPN, you should also add a higher-priority rule that directs local traffic to the main table before it hits the WireGuard rule:
ip rule add from 192.168.1.100 iif lo table main priority 100
For more complex scenarios involving split tunneling, you might want to see how to configure split tunneling with WireGuard VPN on Linux to manage which destinations go through the tunnel.
Maintaining Connectivity During SSH Sessions
One of the biggest challenges when configuring these routes is avoiding a "self-lockout" scenario. If you SSH into the server and your SSH traffic gets routed through the WireGuard tunnel, but the tunnel cannot reach the SSH client, the session will hang. To prevent this, you must exclude your management IP or the SSH source IP from the routing rules.
Add a rule that prioritizes the main routing table for your SSH source IP. For instance, if you connect from 203.0.113.5, add this rule with a lower priority number (higher precedence) than your WireGuard rule:
ip rule add from 203.0.113.5 table main priority 50
This ensures that any packet coming from your admin IP goes directly to the internet gateway, ignoring the WireGuard table. This is particularly important when managing VPS servers remotely, as discussed in guides on routing all internet traffic through WireGuard peers, though here we are doing the opposite for specific sources.
Verifying and Persisting Configuration
After applying the rules, verify the configuration using ip rule show and ip route show table wireguard_table. You should see the custom rule pointing to the wireguard_table and the route within that table pointing to wg0. Test the routing by pinging a destination through the VPN from the specified source IP and checking the return path.
To make these changes persistent across reboots, you should integrate them into your network configuration manager. On Debian-based systems, you can add these commands to /etc/network/interfaces or use NetworkManager dispatcher scripts. Alternatively, many modern distributions support adding routing rules directly in the WireGuard configuration file using the PostUp directive:
[Interface]
...
PostUp = ip rule add from 192.168.1.100 table wireguard_table
PostDown = ip rule del from 192.168.1.100 table wireguard_table
Note that you must also handle the creation of the routing table itself, either by ensuring /etc/iproute2/rt_tables is updated separately or by including the table creation in the PostUp script if it doesn't exist yet. This setup provides a robust way to isolate application traffic without disrupting the broader network functionality of the server.
Frequently Asked Questions
Why does my application lose internet access after setting the route?
This usually happens because the routing rule forces all traffic from that source IP through the VPN, but the VPN does not have a default gateway for non-VPN destinations. You must ensure that your WireGuard configuration includes routes for the specific networks you want to access via the tunnel, or configure a proper default gateway within the WireGuard peer settings if full tunneling is intended for that source.
Can I use iptables instead of ip rule for this purpose?
While iptables can mark packets for policy routing, using ip rule is generally cleaner and easier to manage for simple source-based routing. iptables adds complexity with connection tracking states and requires additional rules to ensure established connections are handled correctly, whereas ip rule operates directly on the routing decision layer.
How do I debug why a packet isn't going through the VPN?
Use tcpdump -i wg0 on the server to see if the packets are arriving at the WireGuard interface. If they are not, check ip rule show to ensure the source IP matches the rule. You can also use tracepath or mtr from the application's perspective to observe the hop-by-hop path and identify where the routing decision diverges.
What happens if the WireGuard interface goes down?
If the interface goes down, the kernel will remove the associated routes automatically. However, the ip rule entries remain. This means subsequent packets from the source IP will attempt to find a route in the wireguard_table, fail, and likely be dropped or fall back to the main table depending on your fallback configurations. It is best practice to include a PostDown script to delete the rules when the tunnel disconnects.
Is this method compatible with systemd-networkd?
Yes, systemd-networkd supports policy routing through its configuration files. You can define [RoutingPolicyRule] sections in your network configuration files to achieve the same result as manual ip rule commands. This integrates better with the system's native networking stack and ensures consistent state management during boot sequences.