A Linux routing table can look completely correct while real traffic still leaves through an unexpected network interface.
You run:
ip route The default gateway looks right. The destination network exists. Both interfaces are up. There are no obvious configuration errors.
Yet an SSH connection replies through the wrong NIC, curl uses an unexpected source address, packets disappear when a second interface is enabled, or traffic captured with tcpdump proves that Linux is not using the path you expected.
This happens because ip route alone does not describe every decision Linux makes when routing a packet.
Modern Linux networking can involve multiple routing tables, policy-routing rules, source-address selection, route metrics, DHCP routes, VPNs, container networks, reverse-path filtering and asymmetric routing.
This guide provides a systematic way to determine exactly why Linux chooses the wrong interface—and how to fix the underlying configuration instead of randomly deleting routes.
ip route Looks Correct?Consider this server:
enp1s0
192.168.10.20/24
Gateway: 192.168.10.1
enp2s0
10.20.30.20/24
Gateway: 10.20.30.1 You run:
ip route and receive:
default via 192.168.10.1 dev enp1s0 metric 100
10.20.30.0/24 dev enp2s0 proto kernel scope link src 10.20.30.20
192.168.10.0/24 dev enp1s0 proto kernel scope link src 192.168.10.20 At first glance, everything appears reasonable.
But suppose an application using 10.20.30.20 attempts to reach an external destination.
Which interface will Linux use?
Looking only at the output above does not necessarily answer the entire question because routing can also depend on the source address and Linux Routing Policy Database.
The ip utility supports both routing-table management and policy-routing rules, while ip route get resolves a route as the kernel sees it rather than simply listing existing entries.
If you need the networking fundamentals behind gateways and route selection first, read CorgiNetwork’s <a href=”https://corginetwork.com/computer-networking-explained/“>Computer Networking Explained</a> and <a href=”https://corginetwork.com/tcp-ip-explained-a-complete-guide-to-tcp-ip-model/“>TCP/IP Explained</a> guides.
ip route as the Final AnswerRunning:
ip route is an excellent starting point.
It is not always the final diagnostic test.
The command normally shows routes in the main routing table. Linux, however, supports multiple routing tables, and those additional tables become particularly important when policy routing is configured.
Check all tables:
ip route show table all You may discover something such as:
default via 192.168.10.1 dev enp1s0 table 100
default via 10.20.30.1 dev enp2s0 table 200 that was not obvious from an ordinary:
ip route command.
This is especially important on multi-homed servers.
CorgiNetwork’s <a href=”https://corginetwork.com/linux-network-tools-guide/“>Linux Network Tools Guide</a> covers the broader collection of Linux commands useful for this type of investigation.
ip route get InsteadOne of the most valuable Linux routing troubleshooting commands is:
ip route get DESTINATION For example:
ip route get 8.8.8.8 Possible output:
8.8.8.8 via 192.168.10.1 dev enp1s0 src 192.168.10.20 This tells us several things:
Destination → 8.8.8.8
Gateway → 192.168.10.1
Interface → enp1s0
Source IP → 192.168.10.20 This is significantly more useful than merely seeing two routes in a routing table.
The ip-route manual describes ip route get as resolving a single route and displaying it as the kernel sees it. It can also incorporate properties such as source address, incoming interface, output interface and packet marks.
Reference: <a href=”https://man7.org/linux/man-pages/man8/ip-route.8.html” target=”_blank” rel=”noopener”>Linux ip-route(8) manual</a>.
This is where multi-interface troubleshooting becomes more interesting.
Run:
ip route get 8.8.8.8 from 192.168.10.20 Then:
ip route get 8.8.8.8 from 10.20.30.20 Suppose both commands return:
via 192.168.10.1 dev enp1s0 You have found an important clue.
Traffic using the second address is still being routed according to a path that exits through the first interface.
That may be perfectly correct for your architecture.
Or it may be exactly the problem you are trying to solve.
If the desired architecture is:
192.168.10.20
↓
enp1s0
↓
192.168.10.1 and:
10.20.30.20
↓
enp2s0
↓
10.20.30.1 you probably need to inspect the policy-routing design rather than adding random static routes.
CorgiNetwork’s <a href=”https://corginetwork.com/netplan-multiple-default-routes-policy-routing/“>Netplan Multiple Default Routes and Policy Routing Guide</a> explains how to build this architecture persistently on Ubuntu.
Administrators often focus entirely on the destination:
Where is the packet going? But the selected source address can also be critical.
Check the addresses:
ip -br addr Example:
enp1s0 UP 192.168.10.20/24
enp2s0 UP 10.20.30.20/24 Now ask Linux:
ip route get 8.8.8.8 The output may include:
src 192.168.10.20 That tells you which source address the kernel expects to use for that route.
This becomes particularly important when:
Sometimes the routing table is not the real problem.
An application may explicitly bind its traffic to an address or interface.
For example, curl allows testing through a particular local interface/address:
curl --interface 192.168.10.20 https://example.com/ Compare it with:
curl --interface 10.20.30.20 https://example.com/ If the first succeeds and the second fails, you now have evidence that the two source paths behave differently.
Do not immediately conclude that the second NIC itself is broken.
Investigate:
Source selection
↓
Policy rules
↓
Routing table
↓
Gateway
↓
Firewall
↓
Upstream routing Run:
ip rule A simple system might resemble:
0: from all lookup local
32766: from all lookup main
32767: from all lookup default But a multi-homed server might contain:
1000: from 192.168.10.20 lookup 100
1100: from 10.20.30.20 lookup 200 Now inspect those tables:
ip route show table 100 and:
ip route show table 200 For example:
Table 100
192.168.10.0/24 dev enp1s0
default via 192.168.10.1 dev enp1s0 while:
Table 200
10.20.30.0/24 dev enp2s0
default via 10.20.30.1 dev enp2s0 This explains a routing decision that might be impossible to understand by looking only at the main table.
The Linux ip manual explicitly exposes rule as the object for managing the routing policy database.
Suppose the desired behavior is:
Traffic sourced from 192.168.10.20
↓
Table 100
↓
enp1s0
Traffic sourced from 10.20.30.20
↓
Table 200
↓
enp2s0 The runtime configuration conceptually involves routes such as:
ip route add 192.168.10.0/24 dev enp1s0 table 100
ip route add default via 192.168.10.1 dev enp1s0 table 100 and:
ip route add 10.20.30.0/24 dev enp2s0 table 200
ip route add default via 10.20.30.1 dev enp2s0 table 200 with policies such as:
ip rule add from 192.168.10.20/32 table 100
ip rule add from 10.20.30.20/32 table 200 These commands illustrate the underlying Linux routing concept. Do not blindly execute them on a production machine; persistent configuration should be designed using the networking system that manages your server.
For Ubuntu using Netplan, use the dedicated <a href=”https://corginetwork.com/netplan-multiple-default-routes-policy-routing/“>CorgiNetwork Netplan policy-routing guide</a> instead of relying on temporary runtime commands.
Now consider a simpler situation.
Run:
ip route and find:
default via 192.168.10.1 dev enp1s0 metric 100
default via 10.20.30.1 dev enp2s0 metric 500 The first route has the lower metric.
If you expected ordinary traffic to use enp2s0, your expectation and the actual route preference do not match.
But be careful:
changing the metric is not automatically the correct solution.
First decide what the architecture should be.
If the requirement is:
Primary connection
+
Backup connection metrics can be useful.
If the requirement is:
Source A → Gateway A
Source B → Gateway B you should investigate policy routing.
Run:
ip route | grep default You might discover:
default via 192.168.10.1 dev enp1s0 proto static metric 100
default via 10.20.30.1 dev enp2s0 proto dhcp metric 100 This is a major clue.
One route may have been configured manually while another arrived through DHCP.
Before deleting either route, determine where it came from.
Possible sources include:
If DHCP behavior is unfamiliar, CorgiNetwork’s <a href=”https://corginetwork.com/dhcp-explained/“>DHCP Explained</a> guide explains how clients obtain network configuration, including gateway information.
Consider this topology:
Internet
|
+----------+----------+
| |
Gateway A Gateway B
| |
enp1s0 enp2s0
\ /
\ /
Linux Server A client connects to the address associated with enp2s0.
The request enters:
Client
↓
Gateway B
↓
enp2s0 But the server’s preferred default route points to Gateway A.
The response becomes:
Server
↓
enp1s0
↓
Gateway A This is asymmetric routing.
Depending on the topology, it can interact badly with:
Symptoms can be particularly confusing:
Ping works
SSH sometimes works
TCP connections hang
One server IP works
Second server IP fails If a specific TCP service is failing, use CorgiNetwork’s <a href=”https://corginetwork.com/netcat-explained/“>Netcat Explained</a> guide to test the port directly.
For example:
nc -vz SERVER_IP 443 Linux also provides reverse-path filtering through rp_filter.
Check:
sysctl net.ipv4.conf.all.rp_filter Then inspect the relevant interfaces:
sysctl net.ipv4.conf.enp1s0.rp_filter
sysctl net.ipv4.conf.enp2s0.rp_filter The Linux kernel documentation defines the main modes as:
0 → no source validation
1 → strict reverse-path validation
2 → loose reverse-path validation In strict mode, an incoming packet can fail validation if its incoming interface is not considered the best reverse path. The kernel documentation specifically notes that loose mode is relevant to asymmetric or otherwise complicated routing environments.
Read the authoritative reference at the <a href=”https://docs.kernel.org/networking/ip-sysctl.html” target=”_blank” rel=”noopener”>Linux Kernel IP sysctl documentation</a>.
However, do not immediately run:
sysctl -w net.ipv4.conf.all.rp_filter=0 just because connectivity is broken.
That can mask a routing-design problem and weaken source validation.
First establish:
rp_filter is actually responsible.Suppose:
ip route get DESTINATION shows the correct interface.
Packets can still be blocked.
Check UFW:
sudo ufw status verbose or nftables:
sudo nft list ruleset A firewall can distinguish between:
Interface
Source IP
Destination IP
Protocol
Port
Connection state So a service can work through enp1s0 but fail through enp2s0 even when both routes are correct.
See CorgiNetwork’s <a href=”https://corginetwork.com/firewalls-explained/“>Firewalls Explained</a> and <a href=”https://corginetwork.com/network-security-basics/“>Network Security Basics</a> for the filtering and security concepts behind these rules.
Imagine that everything works after configuration.
Then the DHCP lease renews.
Suddenly:
ip route contains a new default route.
This can happen because a DHCP-managed interface may receive routing information dynamically.
Look for:
proto dhcp in the routing table.
Example:
default via 10.20.30.1 dev enp2s0 proto dhcp metric 100 If you were expecting only the manually configured default gateway on enp1s0, you have identified a likely source of the unexpected behavior.
Do not repeatedly delete the route manually.
Fix the component generating it.
VPN clients frequently install routes.
Before connecting:
default via 192.168.1.1 dev wlan0 After connecting, you may discover:
0.0.0.0/1 via 10.8.0.1 dev tun0
128.0.0.0/1 via 10.8.0.1 dev tun0 At first glance, the original default route still exists.
Yet most IPv4 destinations now match the two /1 routes because they are more specific than /0.
Traffic therefore goes through:
tun0 instead of the original interface.
This is an excellent example of why:
ip route | grep default is not sufficient.
Always test the actual destination:
ip route get DESTINATION_IP Docker and other container platforms commonly create additional interfaces and networks.
For example:
docker0
br-xxxxxxxx
vethxxxx Inspect:
ip -br link and:
ip route Container networking may also involve NAT and firewall rules.
If the problem affects only containers but not the host, separate the investigation into:
Container
↓
Container route
↓
Bridge
↓
Host networking
↓
NAT / firewall
↓
Physical interface Do not assume the host’s route selection and a container’s network namespace are identical.
Cloud servers add another important layer.
A Linux VM may correctly select:
eth1 but the virtual network may not provide the expected path beyond that interface.
A simplified cloud path is:
Application
↓
Linux routing
↓
Virtual NIC
↓
Cloud subnet
↓
Cloud route table
↓
Security controls
↓
NAT / Internet gateway Therefore:
ip route get 8.8.8.8 can tell you what Linux intends to do.
It cannot by itself prove that the provider’s virtual network will successfully carry that traffic.
CorgiNetwork’s <a href=”https://corginetwork.com/cloud-networking-explained/“>Cloud Networking Explained</a> provides a deeper explanation of this additional network layer.
Eventually, routing troubleshooting should move from configuration to actual packets.
Suppose you expect traffic to leave through enp2s0.
Run:
sudo tcpdump -ni enp2s0 host 203.0.113.20 In another terminal:
sudo tcpdump -ni enp1s0 host 203.0.113.20 Then generate traffic:
curl https://203.0.113.20/ Now observe which interface actually sees the packets.
An even broader capture is:
sudo tcpdump -ni any host 203.0.113.20 This can answer:
Did Linux actually send the packet?
Which interface carried it?
Did a reply return?
Did TCP retransmit?
For deeper packet inspection, see CorgiNetwork’s <a href=”https://corginetwork.com/wireshark-explained/“>Wireshark Explained</a>.
Suppose you expect HTTPS traffic through enp2s0.
You run:
curl https://example.com/ and capture:
enp1s0 → SYN → server:443 This proves the problem occurs before the external network.
Linux itself selected enp1s0.
Investigate:
ip route get SERVER_IP
ip rule
ip route show table all You capture:
enp2s0 → SYN → server:443
enp2s0 → SYN → server:443
enp2s0 → SYN → server:443 but receive no SYN-ACK.
Linux appears to be sending through the intended interface.
Now investigate:
The local route may not be the problem.
Suppose tcpdump shows:
SYN →
← SYN-ACK
ACK → The TCP handshake completed.
If the application still fails, move higher in the stack.
Do not continue modifying routes simply because the application does not work.
This is why CorgiNetwork’s broader <a href=”https://corginetwork.com/tcp-ip-explained-a-complete-guide-to-tcp-ip-model/“>TCP/IP guide</a> is useful: troubleshooting becomes much easier when each test is associated with the layer it actually verifies.
Packet loss or interface errors can also look like routing problems.
Run:
ip -s link show enp1s0 and:
ip -s link show enp2s0 Look for unusual increases in:
errors
dropped
overruns You can also use:
ethtool -S enp2s0 when supported by the driver.
If Linux selects the correct route and sends through the correct interface but the NIC or link is dropping traffic, changing routing policies will not solve the problem.
When the gateway is on the local Ethernet network, Linux must resolve its Layer-2 neighbor information.
Run:
ip neigh You might see:
192.168.10.1 dev enp1s0 lladdr 00:11:22:33:44:55 REACHABLE But problematic states may include:
FAILED or prolonged:
INCOMPLETE If the gateway cannot be resolved at Layer 2, having a correct default route will not make the gateway reachable.
This is another reason a routing table can look perfect while traffic still fails.
When Linux appears to use the wrong interface, use this order.
ip -br link ip -br addr ip route ip route show table all ip rule ip route get DESTINATION_IP ip route get DESTINATION_IP from SOURCE_IP ip neigh ping -I SOURCE_IP DESTINATION_IP nc -vz DESTINATION_IP PORT curl --interface SOURCE_IP https://example.com/ sudo nft list ruleset or the firewall framework actually used by the system.
sysctl net.ipv4.conf.all.rp_filter sudo tcpdump -ni any host DESTINATION_IP At this point, you should be able to say where the failure occurs, rather than simply saying that “Linux routing is broken.”
Traffic uses unexpected interface
|
v
ip route get DESTINATION
|
+------+------+
| |
Wrong interface Correct interface
| |
v v
Check: Capture packets
ip rule |
all tables +---+---+
metrics | |
source IP Leaves Doesn't leave
DHCP routes correctly |
VPN routes | v
| | Firewall /
v | local stack
Fix routing v
Does reply return?
|
+-----+-----+
| |
NO YES
| |
upstream / TCP/TLS/
firewall / application
return path The goal is to progressively narrow the problem.
Administrators see two defaults and immediately run:
ip route del default without identifying which component created the route.
The route may return after:
Find the configuration source first.
This often creates a more complicated routing table without addressing the real issue.
Ask the kernel:
ip route get DESTINATION from SOURCE before adding anything.
rp_filter ImmediatelyReverse-path filtering can matter in asymmetric environments, but disabling it globally without evidence is poor troubleshooting.
The kernel documents strict and loose reverse-path validation specifically so administrators can understand the behavior rather than blindly disabling it.
A more-specific route always deserves attention.
A VPN, for example, may install routes that match a destination more specifically than the visible /0 default.
Routing behavior may differ depending on the source.
Always compare:
ip route get DESTINATION from SOURCE_A with:
ip route get DESTINATION from SOURCE_B on multi-homed systems.
Ping proves only what that specific ICMP test demonstrates.
Your application may:
Test the real application path.
Common causes include a more-preferred route, policy-routing rule, unexpected source address, DHCP-installed route, VPN route, multiple default gateways or application-specific binding.
Run:
ip route get DESTINATION_IP The Linux ip-route documentation states that this resolves the route as the kernel sees it rather than simply displaying the routing-table contents.
Use:
ip route get DESTINATION_IP from SOURCE_IP This is especially valuable on servers with multiple NICs.
ip route different from ip route get?ip route displays routing-table entries, while ip route get performs a route lookup for a specific destination and can account for parameters such as the source address.
Run:
ip route show table all Linux supports multiple routing tables, particularly for policy-routing configurations.
Run:
ip rule Then inspect the tables referenced by the returned rules.
Yes. DHCP can install routes, including a default gateway. Inspect ip route for routes associated with DHCP and identify which network manager generated them.
Yes. A VPN can install more-specific routes that take precedence for particular destination ranges even while the original default route remains visible.
It occurs when traffic takes different paths in opposite directions—for example, a request enters through one interface while the response leaves through another.
rp_filter cause multi-NIC connectivity problems?It can matter in asymmetric or complex routing configurations. The Linux kernel documents strict mode as validating whether the incoming interface is the best reverse path and identifies loose mode as relevant to asymmetric or complicated routing.
When Linux traffic uses the wrong interface even though the routing table looks correct, resist the temptation to immediately add or delete routes.
The first question should be:
What route does the Linux kernel actually select for this packet? Answer it with:
ip route get DESTINATION Then make the test more specific:
ip route get DESTINATION from SOURCE If the result is unexpected, inspect:
ip rule
ip route show table all If the kernel selects the correct route, move to packet-level verification:
sudo tcpdump -ni any host DESTINATION From there, investigate the gateway, firewall, reverse path, upstream network or application instead of continuing to modify a routing table that may already be correct.
For Ubuntu servers where the real requirement is to make each NIC consistently use its own gateway, continue with CorgiNetwork’s <a href=”https://corginetwork.com/netplan-multiple-default-routes-policy-routing/“>Netplan Multiple Default Routes and Policy Routing Guide</a>. For the diagnostic tools used throughout this process, see the <a href=”https://corginetwork.com/linux-network-tools-guide/“>Linux Network Tools Guide</a>, <a href=”https://corginetwork.com/netcat-explained/“>Netcat Explained</a>, and <a href=”https://corginetwork.com/wireshark-explained/“>Wireshark Explained</a>.
AI Agents as Code: Managing AI Agent Configurations, Prompts and Tools with Git AI agents…
Data Center Power Distribution Architecture: UPS, PDU, Redundancy and Rack Power Explained Data center power…
DNSSEC Troubleshooting: Fix SERVFAIL & DNSKEY Errors DNSSEC Troubleshooting: How to Fix SERVFAIL, DS, DNSKEY…
Terraform State Management Explained: Remote State, Locking, Backends and Best Practices Terraform makes infrastructure reproducible…
RTO vs RPO Explained: Disaster Recovery Objectives, Examples and Planning A disaster recovery plan is…
Network Intrusion Detection Explained: NIDS, Sensors, Signatures and Traffic Analysis A firewall can decide whether…
View Comments