A Linux server can have multiple network interfaces, multiple gateways, and multiple routing tables.
At first, ordinary routing may be enough.
As the network grows, you may introduce source-based routing with ip rule. That works extremely well for many multi-homed systems, and CorgiNetwork’s Linux Policy Routing with ip rule and Multiple Routing Tables guide explains that architecture in detail.
But eventually you may need something stronger:
This is where Linux VRF — Virtual Routing and Forwarding — becomes extremely useful.
A Linux VRF associates a virtual network device with a routing table. Physical or virtual interfaces can then be attached to that VRF, allowing their Layer 3 traffic to use the routing domain associated with it.
The official Linux kernel documentation describes the Linux implementation as VRF-lite and specifically identifies multi-tenancy with independent routing tables and different default gateways as one of its use cases.
This guide builds two VRFs from scratch, explains l3mdev routing rules, shows how to run applications inside a VRF, demonstrates packet captures and route lookups, and compares VRF with policy routing, VLANs, and network namespaces
VRF stands for:
Virtual Routing and Forwarding.
The basic concept is simple.
Instead of having every interface depend primarily on one shared routing domain:
Linux
|
Main Routing Table
/ \
eth1 eth2 we can separate routing:
Linux
/ \
VRF BLUE VRF RED
| |
Table 100 Table 200
| |
eth1 eth2 Now each side has its own Layer 3 routing context.
The ip-vrf(8) documentation describes a VRF as providing Layer 3 traffic isolation, analogous to how VLANs provide Layer 2 isolation. Fundamentally, each VRF is associated with a separate routing table.
This distinction is important:
VLAN
↓
Layer 2 segmentation
VRF
↓
Layer 3 routing separation Consider a server with two networks.
Management Network
|
enp1s0
|
Linux
|
enp2s0
|
Production Network The management network has:
10.10.10.20/24
Gateway 10.10.10.1 Production has:
172.16.20.20/24
Gateway 172.16.20.1 You could solve this using policy routing:
Source 10.10.10.20
↓
ip rule
↓
Table 100 and:
Source 172.16.20.20
↓
ip rule
↓
Table 200 That architecture is covered in our Linux Policy Routing guide.
VRF provides another model:
enp1s0
↓
VRF MANAGEMENT
↓
Table 100
enp2s0
↓
VRF PRODUCTION
↓
Table 200 The interface itself becomes associated with a VRF routing domain.
These technologies are related, not mutually exclusive.
Policy routing asks:
Does packet match rule?
↓
Which table should be consulted? For example:
ip rule add from 10.10.10.20 table 100 This works exceptionally well when you need routing decisions based on:
VRF instead organizes interfaces and traffic into Layer 3 routing domains:
Interface
↓
VRF
↓
Routing Table Interestingly, Linux VRF itself integrates with the routing-policy system.
The kernel documentation states that an l3mdev FIB rule directs route lookups to the routing table associated with the VRF device.
So Linux VRF does not eliminate policy routing.
It builds on the same routing infrastructure.
VRF and VLAN solve different problems.
A VLAN primarily provides:
Layer 2 segmentation A VRF provides:
Layer 3 routing separation You can therefore combine them:
Physical NIC
|
VLAN 10
|
VRF BLUE
|
Routing Table 100 and:
Physical NIC
|
VLAN 20
|
VRF RED
|
Routing Table 200 This combination is common in more sophisticated network architectures.
Linux network namespaces provide broader network-stack isolation.
A namespace can have its own:
VRF is narrower.
The kernel documentation notes that VRF affects Layer 3 and above, while Layer 2 tools do not need to be separately run in each VRF. It also explicitly supports putting VRFs inside network namespaces.
Think of the difference as:
VRF
↓
Routing-domain separation versus:
Network Namespace
↓
Broader network-stack isolation Neither is universally superior.
The appropriate tool depends on what you need to isolate.
We will create:
Linux Server
/ \
VRF BLUE VRF RED
| |
Table 100 Table 200
| |
enp1s0 enp2s0
| |
192.168.10.1 10.20.30.1
Gateway Gateway Server addresses:
enp1s0 = 192.168.10.20/24
enp2s0 = 10.20.30.20/24 We want complete routing separation:
BLUE
192.168.10.20
↓
Table 100
↓
192.168.10.1
RED
10.20.30.20
↓
Table 200
↓
10.20.30.1 Before changing anything:
ip -br link Then:
ip -br addr Check routes:
ip route Check policy rules:
ip rule And finally:
ip route show table all Do this before creating a VRF.
You need a baseline.
If the server already routes through an unexpected NIC, diagnose that first with our Linux Traffic Uses the Wrong Interface troubleshooting guide.
Create a VRF device associated with routing table 100:
sudo ip link add vrf-blue type vrf table 100 Bring it up:
sudo ip link set vrf-blue up Inspect it:
ip -d link show vrf-blue The key relationship is:
vrf-blue
↓
table 100 The official kernel configuration syntax follows this same model:
ip link add vrf-blue type vrf table 10
ip link set dev vrf-blue up with the VRF device explicitly associated with a FIB table.
Now create:
sudo ip link add vrf-red type vrf table 200 Bring it up:
sudo ip link set vrf-red up We now have:
vrf-blue → Table 100
vrf-red → Table 200 List VRFs with:
ip vrf show The ip vrf command also supports inspecting VRFs, identifying the VRF associated with a process, listing processes and executing commands within a VRF context.
Attach:
enp1s0 to:
vrf-blue using:
sudo ip link set dev enp1s0 master vrf-blue Now:
enp1s0
↓
vrf-blue
↓
Table 100 Check:
ip link show master vrf-blue The kernel documentation calls this enslaving the interface to the VRF device. When an L3 interface is attached to a VRF, its local and connected routes are moved into the table associated with that VRF.
That behavior is extremely important.
When an interface is moved into a VRF, do not assume every existing route associated with that interface survives unchanged.
The Linux kernel documentation specifically notes that while local and connected routes are moved into the VRF table, additional routes dependent on the interface can be dropped and may need to be reinserted afterward.
Therefore, after:
ip link set dev enp1s0 master vrf-blue immediately inspect:
ip route show table 100 and:
ip route show table all Never perform this operation remotely on your only management interface without a recovery path.
Now:
sudo ip link set dev enp2s0 master vrf-red Inspect:
ip link show master vrf-red Our architecture is now:
enp1s0
↓
vrf-blue
↓
Table 100
enp2s0
↓
vrf-red
↓
Table 200 Use:
ip route show table 100 and:
ip route show table 200 iproute2 also supports:
ip route show vrf vrf-blue and:
ip route show vrf vrf-red The kernel VRF documentation explicitly documents both the VRF name and table-ID forms for displaying routes.
This makes troubleshooting much easier.
For BLUE:
sudo ip route add table 100 default \
via 192.168.10.1 dev enp1s0 For RED:
sudo ip route add table 200 default \
via 10.20.30.1 dev enp2s0 Conceptually:
VRF BLUE
|
Table 100
|
default
|
192.168.10.1 while:
VRF RED
|
Table 200
|
default
|
10.20.30.1 Each routing domain now has its own default path.
l3mdev RuleNow run:
ip rule You may see an l3mdev rule.
Modern Linux kernels use an l3mdev FIB rule to direct traffic into the routing table associated with the relevant Layer 3 master device.
The kernel documentation states that when the first VRF is created, Linux adds an l3mdev rule for IPv4 and IPv6 with a default preference of:
1000 A single rule can service all VRFs.
Conceptually:
Packet associated with VRF
↓
l3mdev rule
↓
VRF-associated routing table
↓
route This is one of the most important pieces of the Linux VRF architecture.
VRF does not prevent you from using ordinary policy routing.
The kernel documentation explicitly notes that higher-priority policy-based routing rules can take precedence over VRF rules.
Therefore:
Custom PBR rule
priority 100
↓
checked before
↓
l3mdev rule
priority 1000 This makes Linux VRF highly flexible.
It also means a mysterious routing problem may actually be caused by an earlier ip rule.
Always inspect:
ip rule when debugging VRF routing.
Do not assume the configuration works.
Ask Linux.
For BLUE:
ip route get vrf vrf-blue 8.8.8.8 For RED:
ip route get vrf vrf-red 8.8.8.8 The kernel documentation explicitly supports:
ip route get vrf NAME ADDRESS for VRF route testing.
This gives you direct evidence about the routing decision.
ip route get vrf MattersImagine:
ip route get vrf vrf-blue 8.8.8.8 returns a route through:
enp1s0 while:
ip route get vrf vrf-red 8.8.8.8 returns:
enp2s0 Now you have confirmed:
Same destination
↓
Different VRF
↓
Different routing table
↓
Different path That is the core value of VRF.
One of the most useful iproute2 features is:
ip vrf exec For example:
sudo ip vrf exec vrf-blue ping 8.8.8.8 Then:
sudo ip vrf exec vrf-red ping 8.8.8.8 You can also test HTTP:
sudo ip vrf exec vrf-blue curl https://example.com/ and:
sudo ip vrf exec vrf-red curl https://example.com/ The current ip-vrf(8) manual documents:
ip vrf exec NAME command... and gives SSH executed inside a named VRF as an example.
ip vrf exec RequirementThere is an important technical detail.
The ip-vrf(8) manual states that the helper requires a kernel with:
CGROUPS and:
CGROUP_BPF enabled.
So if:
ip vrf exec ... does not behave as expected, do not immediately conclude that the VRF routing table itself is broken.
Check platform/kernel support as part of the diagnosis.
Applications can also bind sockets directly to a VRF device.
The kernel documentation describes using:
SO_BINDTODEVICE to bind an application socket to the VRF. Packets through that socket then use the routing table associated with the VRF.
Conceptually:
Application
↓
Socket bound to vrf-blue
↓
Table 100
↓
enp1s0 This is particularly useful for:
Suppose SSH listens normally in the default routing domain.
Will it automatically accept traffic arriving from every VRF?
Not necessarily.
Linux provides settings including:
net.ipv4.tcp_l3mdev_accept and:
net.ipv4.udp_l3mdev_accept The kernel documentation states that these options are disabled by default, allowing socket selection to remain isolated by VRF context unless explicitly changed.
Inspect them:
sysctl net.ipv4.tcp_l3mdev_accept and:
sysctl net.ipv4.udp_l3mdev_accept Do not change these blindly.
First determine whether your application should:
listen inside one VRF or:
serve multiple VRFs Those are different architectures.
One particularly useful feature is the ability to capture traffic using the VRF device.
For example:
sudo tcpdump -ni vrf-blue The kernel documentation explains that packets appear to flow through the VRF device in the IPv4 and IPv6 processing stacks, allowing tcpdump on that device to capture traffic entering and leaving the VRF as a whole.
This is extremely convenient when a VRF contains several interfaces.
Instead of:
capture eth1
capture eth2
capture eth3 you may be able to observe the routing domain at:
vrf-blue For more detailed packet analysis, use CorgiNetwork’s Wireshark Explained guide alongside the VRF capture.
VRF is not a firewall.
But firewall rules can interact with VRF traffic.
The Linux kernel documentation notes that netfilter rules can be applied using the VRF device to target traffic associated with the routing domain.
Therefore, when:
VRF route lookup works but:
connection still fails check the firewall.
For nftables:
sudo nft list ruleset Do not assume that correct VRF routing guarantees application connectivity.
The packet still traverses other parts of the networking stack.
Multi-homed routing can also interact with reverse-path validation.
If an incoming packet arrives through one routing domain while Linux believes its reverse path belongs somewhere else, source validation can become relevant.
Check:
sysctl -a 2>/dev/null | grep '\.rp_filter' If packet captures show incoming traffic but services never receive it, continue with CorgiNetwork’s Linux rp_filter and Asymmetric Routing guide.
That article explains:
rp_filter = 0
rp_filter = 1
rp_filter = 2 and how reverse-route validation can affect multi-homed systems.
Suppose multiple interfaces occupy the same Ethernet network.
You may still encounter unexpected neighbor or ARP behavior.
Inspect:
ip neigh and:
ip neigh show If the wrong interface appears to answer for an IPv4 address or neighboring hosts learn an unexpected MAC mapping, the problem belongs partly to ARP behavior rather than only routing.
Use CorgiNetwork’s Linux ARP Flux: Multiple NICs on the Same Subnet guide for that scenario.
To remove:
enp1s0 from its VRF:
sudo ip link set dev enp1s0 nomaster The kernel documentation states that connected routes move back to the default table and local entries return to the local table when the interface is removed from the VRF.
Immediately verify:
ip route and:
ip route show table all after removing an interface.
Before deleting a VRF, detach or otherwise correctly handle the member interfaces.
Then the VRF device can be removed with the normal link-management model.
For example:
sudo ip link delete vrf-blue Do not perform this casually on a remote production machine.
Removing a routing domain can immediately change reachability.
You run:
ip vrf show and see:
vrf-blue but:
ip vrf exec vrf-blue ping 8.8.8.8 fails.
Inspect:
ip route show vrf vrf-blue A VRF device existing does not guarantee that its routing table contains a usable default route.
Remember:
VRF
↓
Table
↓
Routes All three pieces matter.
You run:
ip link set enp1s0 master vrf-blue and immediately lose connectivity.
One possible reason is that additional routes associated with that interface were not preserved when it moved into the VRF.
The kernel documentation explicitly warns that dependent routes beyond the automatically moved local/connected entries may need to be re-added to the VRF table.
Inspect:
ip route show table 100 before making additional changes.
Suppose:
ip route get vrf vrf-blue DESTINATION looks correct.
But the application cannot communicate.
Now check:
Application socket binding
↓
Firewall
↓
rp_filter
↓
Neighbor resolution
↓
Upstream gateway VRF route correctness is only one layer.
Use:
ss -lntup and:
sudo tcpdump -ni vrf-blue to determine whether the problem is above or below the routing layer.
Remember that higher-priority PBR rules can run before the l3mdev rule.
Inspect:
ip rule Suppose you find:
100: from 10.20.30.20 lookup 500
1000: from all lookup [l3mdev-table] The rule with priority:
100 is evaluated earlier.
Therefore, what appears to be a VRF failure may actually be an ordinary policy-routing rule taking precedence.
This is another reason to understand Linux Policy Routing before deploying complex VRFs.
You configure:
vrf-blue then run:
ping 8.8.8.8 and assume the command uses the BLUE routing table.
It may not.
Explicitly test:
sudo ip vrf exec vrf-blue ping 8.8.8.8 or perform a VRF-specific route lookup:
ip route get vrf vrf-blue 8.8.8.8 Test the routing domain you actually intend to validate.
When VRF connectivity fails, use a consistent sequence.
ip vrf show ip -d link show type vrf ip link show master vrf-blue ip -br addr ip rule ip route show vrf vrf-blue ip route show table all ip route get vrf vrf-blue DESTINATION sudo ip vrf exec vrf-blue ping DESTINATION sudo tcpdump -ni vrf-blue ip neigh sudo nft list ruleset sysctl -a 2>/dev/null | grep '\.rp_filter' ss -lntup This sequence lets you move from:
VRF configuration
↓
routing
↓
actual packets
↓
application instead of randomly changing network settings.
Connection fails
|
v
VRF exists?
/ \
NO YES
| |
Create v
VRF Interface enslaved?
/ \
NO YES
| |
Attach v
Route exists?
/ \
NO YES
| |
Fix table v
Route lookup correct?
/ \
NO YES
| |
Check rules v
Packet leaves?
/ \
NO YES
| |
Firewall / Check
rp_filter upstream Imagine a server used for both management and customer traffic.
Linux Server
/ \
MANAGEMENT PRODUCTION
VRF VRF
| |
Table 100 Table 200
| |
enp1s0 enp2s0
| |
Mgmt GW Prod GW This is easier to reason about than a large collection of unrelated source rules.
An administrator can immediately ask:
Which VRF?
Which table?
Which interface?
Which gateway? That organizational clarity is one of VRF’s biggest operational advantages.
This distinction is important.
A separate routing table is not equivalent to:
complete security sandbox VRF primarily provides Layer 3 routing-domain separation.
Security still requires appropriate controls such as:
Do not treat VRF as a replacement for a firewall.
Likewise:
VRF ≠ VLAN A useful design might combine:
VLAN
↓
Layer 2 separation
↓
VRF
↓
Layer 3 routing separation
↓
Firewall
↓
Security policy Each layer solves a different problem.
Do not deploy VRF merely because it sounds more advanced.
If your only requirement is:
Source IP A → Gateway A
Source IP B → Gateway B a few clean ip rule entries may be easier.
Use the Linux Policy Routing with ip rule and Multiple Routing Tables architecture first.
VRF becomes particularly attractive when the routing domains themselves need to be explicit operational objects.
If you need stronger separation involving:
interfaces
routing
sockets
network stack
process context network namespaces may be a better architectural fit.
VRF and namespaces can also be combined.
The kernel documentation explicitly notes that VRF devices can exist inside network namespaces, allowing separation at several levels.
This gives Linux engineers a hierarchy such as:
Network Namespace
↓
VLAN
↓
VRF
↓
Routing Table That architecture deserves careful design rather than automatic deployment.
Before considering a Linux VRF deployment complete, verify:
✓ VRF devices exist
✓ Every VRF uses the intended table ID
✓ Correct interfaces belong to each VRF
✓ Addresses remain correct
✓ Connected routes exist
✓ Default routes exist where required
✓ ip rule contains expected l3mdev behavior
✓ Higher-priority PBR rules are understood
✓ VRF route lookups return expected paths
✓ Gateways are reachable
✓ Applications use the intended VRF
✓ Firewall rules allow required traffic
✓ rp_filter behavior is understood
✓ ARP/neighbor resolution is correct
✓ Packet captures confirm real paths
✓ Configuration survives reboot
✓ Remote management remains reachable
✓ Recovery access exists The most important test remains the actual packet path.
Linux VRF provides Layer 3 routing-domain separation by associating a VRF device with a separate routing table and allowing interfaces to be attached to that device.
No. VLANs primarily provide Layer 2 segmentation, while VRFs provide Layer 3 routing separation.
No. A network namespace provides broader network-stack isolation. VRF focuses on Layer 3 routing domains, although VRFs can be used inside namespaces.
A basic example is:
sudo ip link add vrf-blue type vrf table 100
sudo ip link set vrf-blue up Then an interface can be attached:
sudo ip link set enp1s0 master vrf-blue This follows the configuration model documented by the Linux kernel.
Use:
ip vrf show Use:
ip route show vrf vrf-blue or inspect its table directly:
ip route show table 100 Both forms are documented by the kernel VRF documentation.
Use:
ip route get vrf vrf-blue DESTINATION For example:
ip route get vrf vrf-blue 8.8.8.8 Use:
sudo ip vrf exec vrf-blue COMMAND For example:
sudo ip vrf exec vrf-blue curl https://example.com/ The ip-vrf(8) manual documents ip vrf exec for executing commands against a named VRF.
Linux’s VRF design allows tcpdump on the VRF device to observe traffic flowing through that VRF domain.
For example:
sudo tcpdump -ni vrf-blue ip rule?Yes. Modern Linux VRF uses an l3mdev FIB rule to direct lookups to the routing table associated with the Layer 3 master device.
Higher-priority policy-routing rules can take precedence over the VRF l3mdev rule.
Linux VRF provides a clean way to transform a complicated multi-interface machine from:
Many interfaces
↓
Many routing rules
↓
One difficult-to-understand network into explicit routing domains:
VRF BLUE
↓
Table 100
↓
Gateway A and:
VRF RED
↓
Table 200
↓
Gateway B The essential commands are:
ip vrf show
ip -d link show type vrf
ip link set INTERFACE master VRF
ip route show vrf VRF
ip route get vrf VRF DESTINATION
ip vrf exec VRF COMMAND
ip rule But VRF should not be viewed in isolation.
If you first need to understand how Linux selects routing tables, read CorgiNetwork’s Linux Policy Routing with ip rule and Multiple Routing Tables.
If packets use an unexpected NIC, continue with Linux Traffic Uses the Wrong Interface.
If reverse-path validation drops legitimate asymmetric traffic, use Linux rp_filter and Asymmetric Routing.
For multiple NICs on the same subnet and unexpected ARP responses, see Linux ARP Flux and Multiple NICs on the Same Subnet.
And for persistent Ubuntu multi-gateway configurations without VRF, use the Netplan Multiple Default Routes and Policy Routing guide.
Together, these articles build a progressively deeper CorgiNetwork learning path:
Wrong interface
↓
Multiple gateways
↓
ARP behavior
↓
Reverse-path filtering
↓
Policy routing
↓
Multiple routing tables
↓
VRF routing domains That progression takes the reader from diagnosing a single wrong route to designing isolated Layer 3 routing domains on Linux.
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…