Linux Policy Routing with ip rule and Multiple Routing Tables: Practical Troubleshooting Guide
Most Linux systems can route traffic perfectly well with one routing table.
Then you add a second network interface.
Or a second Internet connection.
Or a management network.
Or a VPN.
Suddenly, the ordinary destination-based routing model is no longer enough.
You may need traffic from one source address to use Gateway A while traffic from another address uses Gateway B. A management subnet may need its own path. Packets marked by nftables may need a dedicated gateway. A transparent proxy may require special local routes. Or a multi-homed server may need replies to leave through the same network path on which connections arrived.
This is where Linux policy routing becomes essential.
Instead of asking only:
Where is this packet going?
Linux can make routing decisions using additional information such as the packet’s source address, incoming interface, firewall mark, and other selectors.
The two commands at the center of this architecture are:
ip rule
and:
ip route
Understanding how they interact is one of the most valuable skills in advanced Linux networking.
This guide explains Linux’s Routing Policy Database, multiple routing tables, rule priorities, source-based routing, fwmark routing, ip route get, common configuration mistakes, and a practical end-to-end troubleshooting workflow.
What Is Linux Policy Routing?
Traditional routing is primarily destination based.
Suppose Linux receives a packet destined for:
8.8.8.8
The system looks for the most appropriate route toward that destination.
A simple table might contain:
default via 192.168.1.1 dev enp1s0
192.168.1.0/24 dev enp1s0
The answer is straightforward:
8.8.8.8
↓
default route
↓
192.168.1.1
↓
enp1s0
But what if the server has two Internet connections?
ISP A ISP B
| |
Gateway A Gateway B
| |
enp1s0 enp2s0
\ /
\ /
Linux Server
Suppose the addresses are:
enp1s0 = 192.168.10.20
enp2s0 = 10.20.30.20
You want:
Traffic from 192.168.10.20
↓
Gateway A
while:
Traffic from 10.20.30.20
↓
Gateway B
A single ordinary default route does not adequately express that requirement.
You need a routing policy.
Linux provides this through the Routing Policy Database, commonly inspected with:
ip rule
The ip utility exposes rule as a dedicated object alongside route, address, link, neighbor, netns, vrf, and other networking objects.
If your immediate problem is simply that packets are leaving through an unexpected NIC, start with CorgiNetwork’s <a href=”https://corginetwork.com/linux-traffic-uses-wrong-interface/“>Linux Traffic Uses the Wrong Interface: Complete Troubleshooting Guide</a>. It provides the diagnostic foundation before you redesign the routing policy.
Why the Main Routing Table Is Not Always Enough
Run:
ip route
On many systems you might see:
default via 192.168.10.1 dev enp1s0
192.168.10.0/24 dev enp1s0
10.20.30.0/24 dev enp2s0
This normally displays routes from the main routing table.
According to the current ip-route(8) documentation, Linux supports multiple routing tables identified numerically—or by configured names—and ordinary routes normally go into the main table, whose ID is:
254
Linux also maintains a special local table:
255
for local and broadcast addresses. Multiple routing tables become particularly important when policy routing is used.
This means:
ip route
does not necessarily describe your entire routing architecture.
Use:
ip route show table all
when troubleshooting a policy-routed system.
The Important Built-in Routing Tables
Linux reserves several table IDs.
The most important ones for everyday troubleshooting include:
255 → local
254 → main
253 → default
The local table is automatically maintained by the kernel and contains routes associated with local and broadcast addresses. Administrators normally do not manually modify it.
You can inspect it:
ip route show table local
Then inspect the main table:
ip route show table main
Compare that with:
ip route show table all
The difference becomes important once custom tables appear.
Creating Custom Routing Tables
Suppose we want two independent routing domains:
Table 100 → ISP A
Table 200 → ISP B
Linux routing tables can be addressed directly by number:
ip route show table 100
and:
ip route show table 200
You can therefore build a structure such as:
TABLE 100
192.168.10.0/24 dev enp1s0
default via 192.168.10.1 dev enp1s0
and:
TABLE 200
10.20.30.0/24 dev enp2s0
default via 10.20.30.1 dev enp2s0
The routing tables themselves do not answer one crucial question:
Which packets should use Table 100, and which should use Table 200?
That is the job of:
ip rule
Understanding the Routing Policy Database
Run:
ip rule show
A simple system may return something similar to:
0: from all lookup local
32766: from all lookup main
32767: from all lookup default
Read the rules conceptually from higher priority toward lower priority.
A lower numeric priority is processed earlier.
So:
0
has higher priority than:
1000
which has higher priority than:
32766
This detail is critical.
Administrators sometimes assume a larger number means a more important rule.
For ip rule, think:
smaller number
↓
checked earlier
Adding a Source-Based Rule
Suppose traffic sourced from:
192.168.10.20
should use:
table 100
The conceptual rule is:
ip rule add from 192.168.10.20/32 table 100 priority 100
For the second address:
ip rule add from 10.20.30.20/32 table 200 priority 200
Now:
ip rule
might show:
0: from all lookup local
100: from 192.168.10.20 lookup 100
200: from 10.20.30.20 lookup 200
32766: from all lookup main
32767: from all lookup default
Conceptually:
Packet source
|
+── 192.168.10.20
| ↓
| Table 100
|
+── 10.20.30.20
↓
Table 200
This is source-based policy routing.
Build Table 100
For a simplified example:
ip route add 192.168.10.0/24 dev enp1s0 \
src 192.168.10.20 table 100
Then:
ip route add default via 192.168.10.1 \
dev enp1s0 table 100
Inspect it:
ip route show table 100
Expected conceptually:
192.168.10.0/24 dev enp1s0 src 192.168.10.20
default via 192.168.10.1 dev enp1s0
Build Table 200
Now:
ip route add 10.20.30.0/24 dev enp2s0 \
src 10.20.30.20 table 200
Then:
ip route add default via 10.20.30.1 \
dev enp2s0 table 200
Inspect:
ip route show table 200
Expected:
10.20.30.0/24 dev enp2s0 src 10.20.30.20
default via 10.20.30.1 dev enp2s0
We now have:
192.168.10.20
↓
Rule 100
↓
Table 100
↓
enp1s0
10.20.30.20
↓
Rule 200
↓
Table 200
↓
enp2s0
This is far more deterministic than simply installing two competing default routes in the main table.
For an Ubuntu/Netplan implementation of this architecture, see CorgiNetwork’s <a href=”https://corginetwork.com/netplan-multiple-default-routes-policy-routing/“>Netplan Multiple Default Routes and Policy Routing Guide</a>.
Do Not Forget the Connected Network Route
One of the most common mistakes is creating only:
default via 192.168.10.1
inside the custom table.
You also need the routing table to understand how to reach the gateway itself.
For example:
192.168.10.0/24 dev enp1s0
before:
default via 192.168.10.1 dev enp1s0
Otherwise you can encounter errors or unexpected gateway resolution behavior.
Think of each custom routing table as needing enough information to make its own forwarding decisions.
The Most Important Diagnostic Command: ip route get
Never assume the policy works because:
ip rule
looks correct.
Ask the kernel.
Run:
ip route get 8.8.8.8 from 192.168.10.20
You should expect a result consistent with:
via 192.168.10.1 dev enp1s0
Now test the second source:
ip route get 8.8.8.8 from 10.20.30.20
Expected:
via 10.20.30.1 dev enp2s0
The current ip-route(8) interface supports route lookup parameters including source address, incoming interface, outgoing interface, mark, protocol, source port, and destination port.
That makes ip route get considerably more useful than simply staring at routing-table entries.
ip route get vs ip route
The difference is fundamental.
ip route
asks:
What routes are in this table?
While:
ip route get 8.8.8.8 from 10.20.30.20
asks something much closer to:
How would Linux resolve this specific route lookup with these properties?
For troubleshooting, the second question is usually more valuable.
Why ip route Can Look Correct While Traffic Is Wrong
Suppose:
ip route
returns:
default via 192.168.10.1 dev enp1s0
You conclude:
Everything uses enp1s0.
But:
ip rule
contains:
200: from 10.20.30.20 lookup 200
and:
ip route show table 200
contains:
default via 10.20.30.1 dev enp2s0
Then traffic sourced from:
10.20.30.20
may use the second routing table instead.
This is why CorgiNetwork’s <a href=”https://corginetwork.com/linux-traffic-uses-wrong-interface/“>Linux Wrong Interface troubleshooting guide</a> emphasizes checking:
ip rule
ip route show table all
ip route get DESTINATION from SOURCE
rather than relying on the main table alone.
Rule Priority Matters
Consider:
100: from 10.20.30.20 lookup 100
200: from 10.20.30.20 lookup 200
Both rules match the same source.
Which one is considered first?
priority 100
because it has the smaller numeric value.
This can create extremely confusing failures when old rules remain after testing.
Always inspect:
ip rule
before adding another rule.
Do not keep adding policies until connectivity happens to work.
Duplicate Rules Are a Troubleshooting Warning
A system that has been repeatedly modified may contain:
100: from 10.20.30.20 lookup 100
101: from 10.20.30.20 lookup 100
102: from 10.20.30.20 lookup 200
At that point, stop.
Document the intended architecture.
Then determine:
Which source?
Which table?
Which gateway?
Which interface?
Which priority?
A clean policy is easier to troubleshoot than ten overlapping rules.
Policy Routing by Source Network
Rules do not have to match only a single /32 address.
For example:
ip rule add from 10.10.20.0/24 table 200 priority 200
Conceptually:
Any packet sourced from
10.10.20.0/24
↓
Table 200
This can be useful on routers and gateways.
But remember that a broader selector can match much more traffic than intended.
Use the narrowest policy that accurately represents the architecture.
Policy Routing by Destination
Policies can also distinguish destinations.
For example, an architecture might require traffic toward a particular network to use a dedicated table.
Conceptually:
Destination 172.16.50.0/24
↓
Table 300
But before adding policy rules, ask whether an ordinary destination route in the main table would solve the problem more simply.
Do not use policy routing when normal routing already expresses the requirement clearly.
Policy Routing by Incoming Interface
Linux policy rules can also involve an incoming interface.
This becomes useful on routers, gateways and more complex multi-homed systems.
The route lookup interface itself supports iif as one of the lookup properties, and Linux’s route Netlink interface exposes both incoming and outgoing interface attributes.
This allows architectures where:
Traffic entering Interface A
↓
Table A
while:
Traffic entering Interface B
↓
Table B
However, source-based rules are often easier to reason about for multi-address servers.
Choose selectors according to the actual network design.
Firewall Mark Policy Routing
Linux can also route based on a packet mark.
Conceptually:
Packet
↓
nftables assigns mark 10
↓
ip rule sees fwmark 10
↓
Table 100
↓
Gateway A
A rule can look conceptually like:
ip rule add fwmark 10 table 100 priority 100
The Linux kernel’s transparent proxy documentation provides a real example of this model:
fwmark
↓
policy rule
↓
dedicated routing table
and uses a marked rule with a separate table to deliver intercepted traffic locally.
This technique is useful for advanced use cases including:
- transparent proxying;
- multi-WAN routing;
- selected application traffic;
- specialized gateways;
- traffic steering.
Do not introduce packet marking if source- or destination-based routing already solves the problem.
Why fwmark Routing Is Powerful
Imagine that:
Browser traffic
↓
ISP A
Backup traffic
↓
ISP B
Both types of traffic originate from the same IP address.
Source-based routing cannot distinguish them.
But a firewall can classify packets according to relevant characteristics and assign marks.
Then policy routing can map those marks to different tables.
Conceptually:
Traffic classification
↓
mark
↓
ip rule
↓
routing table
↓
gateway
This separates classification from routing.
Multiple Default Gateways: Metrics vs Policy Routing
Suppose:
default via Gateway A metric 100
default via Gateway B metric 500
This can make sense when the requirement is:
Primary gateway
+
Backup gateway
But if your requirement is:
Source A → Gateway A
Source B → Gateway B
metrics alone do not describe the desired behavior.
That is a policy-routing problem.
Use CorgiNetwork’s <a href=”https://corginetwork.com/netplan-multiple-default-routes-policy-routing/“>Netplan Multiple Default Routes guide</a> for the Ubuntu-specific persistent implementation.
Policy Routing and Asymmetric Routing
Consider:
Client
↓
Gateway B
↓
enp2s0
↓
Linux
If Linux responds through:
enp1s0
↓
Gateway A
the forward and return paths differ.
This is asymmetric routing.
Policy routing can often establish a more deterministic return path:
Traffic associated with Source B
↓
Table B
↓
Gateway B
This becomes especially important when:
- stateful firewalls are involved;
- NAT is involved;
- upstream anti-spoofing exists;
- strict reverse-path filtering is enabled.
Policy Routing and rp_filter
Policy routing and reverse-path filtering are closely related in multi-homed systems.
Suppose a packet arrives through:
enp2s0
but the reverse route toward the sender points through:
enp1s0
Strict reverse-path filtering can reject that packet.
The Linux kernel documentation defines strict rp_filter=1 as testing an incoming packet against the FIB and failing the check if the incoming interface is not the best reverse path. Loose mode 2 instead tests whether the source is reachable through any interface.
Before weakening reverse-path validation, verify whether policy routing should correct the route.
CorgiNetwork’s <a href=”https://corginetwork.com/linux-rp-filter-asymmetric-routing/“>Linux rp_filter and Asymmetric Routing Guide</a> covers this problem in detail.
Policy Routing and ARP Flux
Policy routing can also interact with ARP behavior.
Consider two NICs on the same subnet:
enp1s0 → 192.168.50.10
enp2s0 → 192.168.50.20
You may want each address to remain associated with its intended interface.
Linux ARP behavior does not automatically enforce that simple mental model.
In advanced configurations, routing decisions and ARP filtering need to agree.
If neighboring systems learn an unexpected MAC address for one of your IPs, see CorgiNetwork’s <a href=”https://corginetwork.com/linux-arp-flux-multiple-nics-same-subnetpolicy-routing/“>Linux ARP Flux: Multiple NICs on the Same Subnet</a> guide.
This gives our troubleshooting chain:
ARP behavior
↓
Incoming interface
↓
Policy rule
↓
Routing table
↓
Gateway
↓
Reverse-path validation
The Main Table Still Matters
Policy routing does not mean the main table becomes irrelevant.
Suppose your custom rule matches only:
from 10.20.30.20
Other traffic may eventually fall through to:
lookup main
Therefore:
ip route
remains important.
The correct mental model is not:
Policy routing replaces normal routing
but rather:
Policy rules determine
which routing lookup applies
The main table is still one possible lookup target.
What Happens When a Rule Does Not Match?
Suppose:
100: from 192.168.10.20 lookup 100
200: from 10.20.30.20 lookup 200
32766: from all lookup main
A packet sourced from:
172.16.10.50
matches neither rule 100 nor rule 200.
Processing continues until another rule applies.
Eventually:
from all lookup main
can match.
This fall-through behavior is why rule order is so important.
A Routing Table Can Fail to Produce a Route
Another subtle point: selecting a routing table does not automatically guarantee that table can resolve every destination.
Suppose:
Rule 100
↓
Table 100
but Table 100 contains only:
192.168.10.0/24 dev enp1s0
with no default route.
A lookup for:
8.8.8.8
cannot be satisfied by that route alone.
Policy-routing troubleshooting therefore requires examining both:
RULE
and:
TABLE CONTENT
Never inspect one without the other.
throw Routes: An Advanced Policy-Routing Tool
Linux supports a special route type called:
throw
The current ip-route(8) documentation describes a throw route as terminating lookup in that table as though no usable route were found, allowing policy-routing behavior to continue appropriately.
This is an advanced tool and usually unnecessary for basic two-NIC source routing.
But it demonstrates an important concept:
routing tables are part of a larger rule-driven lookup process.
They are not isolated static lists.
Custom Routing Table Names
Numeric IDs work perfectly:
ip route show table 100
But administrators may prefer readable names.
Depending on the system, routing-table names can be defined through the iproute2 routing-table configuration files. The current manual documents table names via:
/usr/lib/iproute2/rt_tables
or:
/etc/iproute2/rt_tables
with the latter taking precedence when present.
Conceptually:
100 isp_a
200 isp_b
Then commands become easier to read:
ip route show table isp_a
and:
ip route show table isp_b
Readable naming can significantly improve production troubleshooting.
A Practical Two-NIC Example
Assume:
NIC A
Interface: enp1s0
Address: 192.168.10.20/24
Gateway: 192.168.10.1
Table: 100
NIC B
Interface: enp2s0
Address: 10.20.30.20/24
Gateway: 10.20.30.1
Table: 200
Desired behavior:
192.168.10.20
↓
enp1s0
↓
192.168.10.1
10.20.30.20
↓
enp2s0
↓
10.20.30.1
Runtime demonstration:
ip route add 192.168.10.0/24 \
dev enp1s0 src 192.168.10.20 table 100
ip route add default \
via 192.168.10.1 dev enp1s0 table 100
Then:
ip route add 10.20.30.0/24 \
dev enp2s0 src 10.20.30.20 table 200
ip route add default \
via 10.20.30.1 dev enp2s0 table 200
Add the policies:
ip rule add from 192.168.10.20/32 \
table 100 priority 100
ip rule add from 10.20.30.20/32 \
table 200 priority 200
Then verify everything.
Verify the Rules
Run:
ip rule
Look for:
100: from 192.168.10.20 lookup 100
200: from 10.20.30.20 lookup 200
Do not proceed merely because the commands returned no error.
Verify Table 100
ip route show table 100
Expected conceptually:
192.168.10.0/24 dev enp1s0 src 192.168.10.20
default via 192.168.10.1 dev enp1s0
Verify Table 200
ip route show table 200
Expected:
10.20.30.0/24 dev enp2s0 src 10.20.30.20
default via 10.20.30.1 dev enp2s0
Verify the Kernel’s Decision
Test path A:
ip route get 8.8.8.8 from 192.168.10.20
Test path B:
ip route get 8.8.8.8 from 10.20.30.20
This is where configuration becomes evidence.
If both return the same interface, something is wrong with:
rule matching
table contents
priority
source selection
Test the Real Traffic
A route lookup is essential, but the application path should also be tested.
For example:
curl --interface 192.168.10.20 https://example.com/
Then:
curl --interface 10.20.30.20 https://example.com/
You can also test basic connectivity:
ping -I 192.168.10.20 8.8.8.8
and:
ping -I 10.20.30.20 8.8.8.8
Remember that successful ICMP does not prove every TCP application will work.
Verify with tcpdump
Capture interface A:
sudo tcpdump -ni enp1s0 host 8.8.8.8
Capture interface B:
sudo tcpdump -ni enp2s0 host 8.8.8.8
Then generate traffic from each source.
This lets you verify:
Source A → Interface A
Source B → Interface B
rather than assuming the rules behave as intended.
For deeper packet inspection, CorgiNetwork’s <a href=”https://corginetwork.com/wireshark-explained/“>Wireshark Explained</a> guide provides the packet-analysis layer of this troubleshooting workflow.
Common Mistake: Using Only Two Default Routes
Administrators sometimes configure:
default via Gateway A metric 100
default via Gateway B metric 200
and expect:
Source A → Gateway A
Source B → Gateway B
That is not what route metrics express.
Metrics primarily provide preference between routes.
If the decision depends on source identity, use policy routing.
Common Mistake: Forgetting Rule Priority
Suppose:
100: from all lookup main
200: from 10.20.30.20 lookup 200
The broad rule may be encountered before the specific policy you intended.
Always inspect the entire RPDB:
ip rule
and ask:
Which rule matches first?
Do not look only for whether your custom rule exists.
Common Mistake: Adding Rules Without Routes
This:
ip rule add from 10.20.30.20 table 200
does not magically populate:
table 200
Inspect:
ip route show table 200
A rule chooses a table.
The table still needs useful routes.
Common Mistake: Adding Routes Without Rules
The opposite also occurs.
You create:
Table 200
default via 10.20.30.1
but no rule directs relevant traffic there.
The table exists but may never be selected for the traffic you intended.
Again:
RULE
↓
TABLE
↓
ROUTE
All three matter.
Common Mistake: Ignoring the Gateway’s Connected Route
A table with:
default via 10.20.30.1
needs a coherent way to reach:
10.20.30.1
Do not forget the directly connected subnet route where required.
Common Mistake: Testing Only ip route
If policy routing exists, this is insufficient:
ip route
Use:
ip rule
plus:
ip route show table all
plus:
ip route get DESTINATION from SOURCE
Those three commands together reveal far more about the actual decision process.
Common Mistake: Disabling rp_filter to Hide a Bad Route
Suppose strict reverse-path filtering rejects traffic because your return route points through the wrong NIC.
You disable it:
rp_filter = 0
and traffic starts working.
That does not necessarily mean disabling source validation was the best solution.
You may simply have allowed an inconsistent routing architecture to continue operating.
First examine the policy routing.
For the complete distinction between strict, loose and disabled reverse-path validation, use CorgiNetwork’s <a href=”https://corginetwork.com/linux-rp-filter-asymmetric-routing/“>Linux rp_filter Explained</a> guide.
Common Mistake: Changing Production Routing Remotely Without a Recovery Path
Routing changes can disconnect your SSH session instantly.
This is especially dangerous when changing:
default routes
ip rules
management interfaces
policy priorities
Before modifying a remote production server, have:
- console access;
- provider recovery console;
- out-of-band management;
- tested rollback commands;
- a maintenance window where appropriate.
A technically correct policy is not useful if you lose access halfway through deploying it.
Runtime Configuration vs Persistent Configuration
Commands such as:
ip rule add ...
and:
ip route add ... table 100
are excellent for understanding and controlled runtime testing.
They are not automatically a persistent configuration strategy.
Persistent implementation depends on the distribution and network manager:
Netplan
NetworkManager
systemd-networkd
distribution-specific network configuration
For Ubuntu systems using Netplan, continue with CorgiNetwork’s <a href=”https://corginetwork.com/netplan-multiple-default-routes-policy-routing/“>Netplan Policy Routing Guide</a> after you understand the underlying Linux behavior.
This article deliberately focuses on how the Linux routing engine works, while the Netplan article covers persistent Ubuntu configuration.
That separation also avoids keyword cannibalization between the two pages.
Advanced Alternative: Linux VRF
As networks become more complex, manually maintaining many source-based policy rules may not always be the cleanest architecture.
Linux also supports Virtual Routing and Forwarding (VRF).
The kernel documentation explains that a VRF device combined with ip rule can create separate Layer-3 routing domains, each with its own routing table. One documented use case is multi-tenancy where different tenants require separate routing tables and different default gateways.
Conceptually:
VRF BLUE
↓
Table 100
↓
Gateway A
VRF RED
↓
Table 200
↓
Gateway B
VRF deserves its own article because it represents a broader architectural approach rather than merely another ip rule.
Complete Policy Routing Troubleshooting Workflow
When policy routing behaves unexpectedly, use this sequence.
1. Identify interfaces
ip -br link
2. Identify addresses
ip -br addr
3. Inspect the main table
ip route
4. Inspect every policy rule
ip rule
5. Inspect every routing table
ip route show table all
6. Inspect specific custom tables
ip route show table 100
ip route show table 200
7. Test source A
ip route get DESTINATION from SOURCE_A
8. Test source B
ip route get DESTINATION from SOURCE_B
9. Check reverse-path filtering
sysctl -a 2>/dev/null | grep '\.rp_filter'
10. Inspect neighbors
ip neigh
11. Capture actual traffic
sudo tcpdump -ni any host DESTINATION
12. Test the application path
curl --interface SOURCE_IP https://example.com/
or for a TCP service:
nc -vz DESTINATION PORT
This sequence moves from configuration to actual packet behavior.
Policy Routing Diagnostic Map
Traffic uses wrong path
|
v
ip rule
|
v
Which rule matches first?
|
+----+----+
| |
Wrong Correct
| |
v v
Fix rule Inspect selected table
priority |
v
Does table contain
correct route?
|
+----+----+
| |
NO YES
| |
v v
Fix table ip route get
|
v
Correct interface?
|
+----+----+
| |
NO YES
| |
v v
Recheck rule Capture
selectors packets
|
v
Check rp_filter,
firewall and
upstream path
How to Document Policy Routing Properly
On production servers, document the design in a simple matrix.
For example:
| Source | Interface | Gateway | Table | Rule Priority |
|---|---|---|---|---|
192.168.10.20 |
enp1s0 |
192.168.10.1 |
100 |
100 |
10.20.30.20 |
enp2s0 |
10.20.30.1 |
200 |
200 |
This simple table can save hours of troubleshooting later.
When you see:
priority 200 → table 200
you immediately know why it exists.
Production Validation Checklist
Before declaring policy routing complete, verify:
✓ Interfaces have expected addresses
✓ Connected routes exist
✓ Every custom table contains expected routes
✓ Every rule references the intended table
✓ Rule priorities are intentional
✓ Source-specific lookups choose correct interfaces
✓ Gateways are reachable
✓ ARP/neighbor resolution works
✓ rp_filter behavior is understood
✓ Firewall permits expected traffic
✓ Real packets use intended interfaces
✓ Configuration survives reboot
✓ Remote-management path remains reachable
✓ Routing design is documented
Do not stop at:
ip rule looks correct
The packet path is the final test.
Frequently Asked Questions
What is Linux policy routing?
Linux policy routing allows route selection to be influenced by policies beyond a simple destination lookup. Rules can direct traffic toward different routing tables according to properties such as source addresses and other supported selectors.
What does ip rule do?
ip rule manages the Routing Policy Database used to determine which routing lookup should be applied to matching traffic.
How do I see Linux policy-routing rules?
Run:
ip rule show
or simply:
ip rule
How do I see all Linux routing tables?
Run:
ip route show table all
The ip-route(8) documentation supports table all for listing routes across routing tables.
What is the Linux main routing table?
The main table has ID:
254
Normal routes are typically inserted there unless another table is explicitly selected.
What is routing table 255?
Table 255 is the local table.
The kernel maintains it automatically for local and broadcast routes, and administrators generally do not need to modify it.
Can Linux have multiple default routes?
Yes.
Multiple defaults can exist in the same or different routing tables. The correct architecture depends on whether you need preference/failover, source-specific routing, ECMP, or another routing model.
What is the difference between route metrics and policy routing?
Metrics express preference among applicable routes.
Policy routing allows different traffic to be directed to different routing tables according to matching rules.
How do I route traffic from one IP through a specific gateway?
A common architecture uses:
source address
↓
ip rule
↓
custom routing table
↓
specific gateway
The exact persistent implementation depends on your Linux networking stack.
How do I test a source-based route?
Use:
ip route get DESTINATION from SOURCE
For example:
ip route get 8.8.8.8 from 10.20.30.20
The route-get interface supports source and several other lookup parameters.
Does ip route show all routing tables?
Not by default.
Use:
ip route show table all
when investigating policy routing.
Why does my custom routing table exist but never get used?
A common reason is that no matching ip rule directs the relevant traffic toward that table.
Check:
ip rule
and verify rule priority.
Why does my ip rule exist but traffic still fails?
Inspect the selected routing table:
ip route show table TABLE_ID
The table may lack a connected route, default route, or another route required to reach the destination.
Does policy routing interact with rp_filter?
Yes, especially in multi-homed and asymmetric-routing configurations. Strict reverse-path validation evaluates incoming traffic against routing information, so inconsistent return paths can cause packet drops. The Linux kernel documents strict and loose reverse-path modes explicitly.
Can policy routing use firewall marks?
Yes.
A packet mark can be used by policy rules to direct marked traffic into a dedicated routing table. Linux’s kernel documentation demonstrates this pattern for transparent proxy routing.
Conclusion
Linux policy routing becomes essential when one routing table can no longer describe how traffic should move through a system.
The core model is simple:
PACKET
↓
POLICY RULE
↓
ROUTING TABLE
↓
ROUTE
↓
GATEWAY / INTERFACE
For a two-NIC server, that can become:
Source A
↓
Rule A
↓
Table A
↓
Gateway A
Source B
↓
Rule B
↓
Table B
↓
Gateway B
The commands you should remember are:
ip rule
ip route
ip route show table all
ip route show table TABLE_ID
ip route get DESTINATION from SOURCE
But the most important lesson is not to add rules blindly.
When Linux uses an unexpected path, determine which rule matched, which table was selected, which route that table returned, and what the real packets did afterward.
If Linux is choosing the wrong NIC, continue with CorgiNetwork’s <a href=”https://corginetwork.com/linux-traffic-uses-wrong-interface/“>Linux Traffic Uses the Wrong Interface</a>.
For persistent Ubuntu source routing, use <a href=”https://corginetwork.com/netplan-multiple-default-routes-policy-routing/“>Netplan Multiple Default Routes and Policy Routing</a>.
If incoming packets disappear because the return path differs, use <a href=”https://corginetwork.com/linux-rp-filter-asymmetric-routing/“>Linux rp_filter and Asymmetric Routing</a>.
And when multiple interfaces occupy the same subnet and neighboring hosts learn unexpected MAC mappings, continue with <a href=”https://corginetwork.com/linux-arp-flux-multiple-nics-same-subnetpolicy-routing/“>Linux ARP Flux and Multiple NICs on the Same Subnet</a>.
These articles now form a tightly connected CorgiNetwork advanced Linux networking cluster covering the complete path from ARP resolution → interface selection → policy rules → routing tables → gateways → reverse-path validation → packet-level troubleshooting.