Linux Inter-VLAN Routing with Network Namespaces: Build a Virtual Router Between VLANs
VLANs are excellent for separating devices into independent Layer 2 broadcast domains.
But isolation creates another requirement:
What happens when devices in different VLANs need to communicate?
Consider a Linux environment containing:
VLAN 10 → Application Servers
VLAN 20 → Database Servers
VLAN 30 → Management
Devices inside VLAN 10 can communicate with other devices in VLAN 10 through Layer 2 switching.
However:
VLAN 10
X
VLAN 20
cannot communicate directly through ordinary bridge forwarding.
Communication between those networks requires Layer 3 routing.
Traditional enterprise networks commonly solve this with a router or Layer 3 switch. Linux can perform the same routing function using network namespaces, virtual Ethernet interfaces, Linux bridges, VLAN interfaces, IP forwarding, routing tables, and firewall policies.
In this practical guide, we will build a complete Linux inter-VLAN routing lab and transform a Linux machine into a virtual router connecting two isolated VLANs.
If you have not built a VLAN-aware Linux bridge before, start with CorgiNetwork’s Linux Bridge VLAN Filtering Explained because this guide builds directly on those concepts.
For the underlying bridge architecture, read Linux Bridge Networking with Network Namespaces and veth.
What Is Inter-VLAN Routing?
A VLAN creates a separate Layer 2 broadcast domain.
Suppose we have:
VLAN 10
192.168.10.0/24
and:
VLAN 20
192.168.20.0/24
A host in VLAN 10 might use:
192.168.10.10
while a host in VLAN 20 uses:
192.168.20.20
Because these addresses belong to different IP networks, traffic between them must pass through a Layer 3 routing device.
Conceptually:
Host A
192.168.10.10
|
VLAN 10
|
v
Router
|
v
VLAN 20
|
Host B
192.168.20.20
That process is called inter-VLAN routing.
Linux includes an IP routing implementation and can therefore perform the Layer 3 function required by this design.
Why the Linux Bridge Alone Is Not Enough
A Linux bridge is fundamentally a Layer 2 component.
The official Linux kernel Ethernet Bridging documentation describes a bridge as connecting network segments and forwarding Ethernet frames according to Layer 2 information.
When VLAN filtering is enabled, Linux additionally uses the VLAN information when determining appropriate bridge forwarding.
Our previous Linux Bridge VLAN Filtering guide created this architecture:
Linux Bridge
vlan_filtering=1
/ \
/ \
VLAN 10 VLAN 20
| |
Host A Host B
The entire purpose of the VLAN boundary is to prevent those two Layer 2 domains from behaving as one LAN.
Therefore, the correct solution is not to defeat VLAN isolation.
We add a router.
VLAN 10
|
|
Router
|
|
VLAN 20
This preserves Layer 2 isolation while permitting controlled Layer 3 communication.
Layer 2 vs Layer 3
This distinction is central to the entire lab.
Inside VLAN 10
Communication between two VLAN 10 hosts follows approximately:
Host A
↓
ARP
↓
Ethernet
↓
Linux Bridge
↓
Host B
No router is required when the destination is directly reachable within the same subnet.
Between VLAN 10 and VLAN 20
The path changes:
Host A
↓
Default Gateway
↓
Router
↓
Routing Table
↓
VLAN 20
↓
Host B
This is Layer 3 routing.
A useful troubleshooting rule is:
Same subnet/VLAN
↓
Investigate Layer 2 first
Different subnet
↓
Investigate gateway and routing
Our Linux Inter-VLAN Routing Lab
We will build:
Linux Host
+----------------+
| br0 |
| VLAN Filtering |
+----------------+
/ \
/ \
VLAN 10 VLAN 20
| |
ns10 ns20
192.168.10.10 192.168.20.20
\ /
\ /
\ /
Router Namespace
ns-rtr
/ \
192.168.10.1 192.168.20.1
The logical Layer 3 topology is:
192.168.10.0/24
Host10
192.168.10.10
|
|
192.168.10.1
Router
192.168.20.1
|
|
Host20
192.168.20.20
192.168.20.0/24
This is effectively a small virtual enterprise network inside one Linux host.
Why Use Network Namespaces?
A Linux network namespace provides its own networking context.
The ip-netns(8) documentation describes a namespace as logically another copy of the network stack with its own routes, firewall rules, and network devices.
That makes namespaces ideal for this project.
Instead of needing:
3 physical computers
+
1 switch
+
1 router
we can simulate the environment inside one Linux machine.
CorgiNetwork’s Linux Network Namespaces Explained covers this architecture in depth.
Step 1: Create the Host Namespaces
Create VLAN 10’s host:
sudo ip netns add ns10
Create VLAN 20’s host:
sudo ip netns add ns20
Create the router:
sudo ip netns add ns-rtr
Verify:
ip netns list
You should see:
ns10
ns20
ns-rtr
Bring up loopback:
sudo ip netns exec ns10 ip link set lo up
sudo ip netns exec ns20 ip link set lo up
sudo ip netns exec ns-rtr ip link set lo up
The ip netns exec command executes networking commands inside the selected namespace.
Step 2: Create the VLAN-Aware Bridge
Create:
sudo ip link add br0 type bridge
Enable VLAN filtering:
sudo ip link set dev br0 type bridge vlan_filtering 1
Bring it up:
sudo ip link set br0 up
Verify:
ip -d link show br0
Look for:
vlan_filtering 1
The Linux kernel bridge documentation notes that VLAN filtering is disabled by default and that, once enabled, forwarding takes VLAN information into account.
Step 3: Create VLAN 10’s veth Pair
Create:
sudo ip link add veth10-br type veth peer name veth10
Move one endpoint into ns10:
sudo ip link set veth10 netns ns10
Attach the other endpoint to the bridge:
sudo ip link set veth10-br master br0
Bring the bridge-facing side up:
sudo ip link set veth10-br up
Inside the namespace:
sudo ip netns exec ns10 ip link set veth10 up
The topology is now:
ns10
|
veth10
|
veth10-br
|
br0
Step 4: Make It a VLAN 10 Access Port
Configure:
sudo bridge vlan add dev veth10-br vid 10 pvid untagged
Inspect:
bridge vlan show
Conceptually:
ns10
|
untagged Ethernet
|
veth10-br
|
PVID 10
|
VLAN 10
Assign the host IP:
sudo ip netns exec ns10 \
ip addr add 192.168.10.10/24 dev veth10
Verify:
sudo ip netns exec ns10 ip -br addr
Step 5: Create VLAN 20’s Host
Create:
sudo ip link add veth20-br type veth peer name veth20
Move:
sudo ip link set veth20 netns ns20
Attach:
sudo ip link set veth20-br master br0
Bring up:
sudo ip link set veth20-br up
sudo ip netns exec ns20 ip link set veth20 up
Configure VLAN 20:
sudo bridge vlan add dev veth20-br vid 20 pvid untagged
Assign:
sudo ip netns exec ns20 \
ip addr add 192.168.20.20/24 dev veth20
We now have:
ns10 ns20
192.168.10.10 192.168.20.20
| |
VLAN 10 VLAN 20
\ /
\ /
br0
They should remain separated at Layer 2.
Step 6: Verify VLAN Isolation Before Routing
From ns10:
sudo ip netns exec ns10 \
ping -c 3 192.168.20.20
This should not suddenly work merely because both endpoints share a bridge.
Why?
They belong to:
192.168.10.0/24
and:
192.168.20.0/24
and they are also separated into different VLANs.
At this stage ns10 does not even have a router capable of reaching VLAN 20.
Check:
sudo ip netns exec ns10 ip route
You should primarily see the directly connected VLAN 10 network.
This is our baseline.
Always establish the broken/isolated state before adding routing.
Step 7: Connect the Router Namespace
Now create another veth pair:
sudo ip link add rtr-trunk type veth peer name rtr0
Move the router endpoint:
sudo ip link set rtr0 netns ns-rtr
Attach the host endpoint:
sudo ip link set rtr-trunk master br0
Bring both sides up:
sudo ip link set rtr-trunk up
sudo ip netns exec ns-rtr ip link set rtr0 up
The topology becomes:
br0
/ | \
/ | \
VLAN10 VLAN20 rtr-trunk
| | |
ns10 ns20 ns-rtr
Unlike our host access ports, the router connection must carry multiple VLANs.
It therefore behaves as a trunk.
Step 8: Allow VLAN 10 and VLAN 20 on the Router Trunk
Add VLAN 10:
sudo bridge vlan add dev rtr-trunk vid 10
Add VLAN 20:
sudo bridge vlan add dev rtr-trunk vid 20
Verify:
bridge vlan show
Conceptually:
veth10-br
|
VLAN 10 access
veth20-br
|
VLAN 20 access
rtr-trunk
|
+--+--+
| |
10 20
tagged
This is the virtual equivalent of connecting a router to a managed switch through an 802.1Q trunk.
Step 9: Create VLAN Interfaces Inside the Router
Inside ns-rtr, create VLAN 10:
sudo ip netns exec ns-rtr \
ip link add link rtr0 name rtr0.10 type vlan id 10
Create VLAN 20:
sudo ip netns exec ns-rtr \
ip link add link rtr0 name rtr0.20 type vlan id 20
Bring them up:
sudo ip netns exec ns-rtr ip link set rtr0.10 up
sudo ip netns exec ns-rtr ip link set rtr0.20 up
Now the router has:
rtr0
|
+---- rtr0.10 → VLAN 10
|
+---- rtr0.20 → VLAN 20
This design is often called a router-on-a-stick architecture because multiple VLANs reach one routing device through a tagged trunk.
Step 10: Give the Router Gateway Addresses
Configure VLAN 10:
sudo ip netns exec ns-rtr \
ip addr add 192.168.10.1/24 dev rtr0.10
Configure VLAN 20:
sudo ip netns exec ns-rtr \
ip addr add 192.168.20.1/24 dev rtr0.20
Inspect:
sudo ip netns exec ns-rtr ip -br addr
Expected conceptually:
rtr0.10 192.168.10.1/24
rtr0.20 192.168.20.1/24
The router is now directly connected to both networks.
Step 11: Inspect the Router’s Routing Table
Run:
sudo ip netns exec ns-rtr ip route
You should see routes equivalent to:
192.168.10.0/24 dev rtr0.10
192.168.20.0/24 dev rtr0.20
No static route between these networks is required because both are directly connected to the router.
The Linux ip route interface manipulates kernel routing-table entries and supports directly connected and gateway-based routes.
The routing decision is now straightforward:
Destination 192.168.10.x
↓
rtr0.10
Destination 192.168.20.x
↓
rtr0.20
Step 12: Test Each Host’s Gateway
From VLAN 10:
sudo ip netns exec ns10 \
ping -c 3 192.168.10.1
From VLAN 20:
sudo ip netns exec ns20 \
ping -c 3 192.168.20.1
Do not continue until both tests work.
If:
ns10 → 192.168.10.1
fails, inter-VLAN routing is not yet the problem.
You have a Layer 2/VLAN issue.
Likewise, failure of:
ns20 → 192.168.20.1
should send you back to VLAN 20 configuration.
Step 13: Configure Default Gateways
Tell ns10 that destinations outside its subnet should use:
192.168.10.1
Run:
sudo ip netns exec ns10 \
ip route add default via 192.168.10.1
For ns20:
sudo ip netns exec ns20 \
ip route add default via 192.168.20.1
Verify VLAN 10:
sudo ip netns exec ns10 ip route
Expected:
192.168.10.0/24 dev veth10
default via 192.168.10.1
VLAN 20:
sudo ip netns exec ns20 ip route
Expected:
192.168.20.0/24 dev veth20
default via 192.168.20.1
Now each endpoint knows where to send traffic destined for another network.
Step 14: Ask Linux Which Route It Will Use
From ns10:
sudo ip netns exec ns10 \
ip route get 192.168.20.20
The result should point toward:
192.168.10.1
Now ask the router:
sudo ip netns exec ns-rtr \
ip route get 192.168.20.20
It should select:
rtr0.20
The ip route get command is particularly valuable because it asks the kernel to resolve a specific route lookup instead of merely listing routes.
This is also one of the core techniques in CorgiNetwork’s Linux Policy Routing with ip rule and Multiple Routing Tables.
Step 15: Enable IPv4 Forwarding
The router owns addresses on both networks, but it must also be allowed to forward packets between them.
Check inside the router namespace:
sudo ip netns exec ns-rtr \
sysctl net.ipv4.ip_forward
Enable it for this lab:
sudo ip netns exec ns-rtr \
sysctl -w net.ipv4.ip_forward=1
Verify:
sudo ip netns exec ns-rtr \
sysctl net.ipv4.ip_forward
Expected:
net.ipv4.ip_forward = 1
The distinction is important:
Router has both networks
≠
Router necessarily forwards packets
IP forwarding turns the namespace from a multi-homed endpoint into an IPv4 forwarding device for this lab.
Step 16: Test Inter-VLAN Routing
Now run:
sudo ip netns exec ns10 \
ping -c 4 192.168.20.20
The packet path is:
ns10
192.168.10.10
|
v
192.168.10.1
ns-rtr
|
routing lookup
|
v
192.168.20.1
|
v
ns20
192.168.20.20
Then test the reverse direction:
sudo ip netns exec ns20 \
ping -c 4 192.168.10.10
If both succeed, you have built a functioning Linux inter-VLAN router.
What Actually Happens to the Packet?
Suppose ns10 sends:
Source:
192.168.10.10
Destination:
192.168.20.20
The source compares the destination against:
192.168.10.0/24
The destination is outside the local subnet.
Therefore:
Destination is remote
↓
Use default gateway
↓
192.168.10.1
The host first needs the gateway’s MAC address.
It performs ARP for:
192.168.10.1
not for:
192.168.20.20
This distinction is extremely important.
The Router Receives the Packet
The router receives:
SRC 192.168.10.10
DST 192.168.20.20
It checks its routing table.
Because:
192.168.20.0/24
is directly connected through:
rtr0.20
the router forwards the packet there.
It then resolves the destination’s Layer 2 address on VLAN 20 if necessary.
Conceptually:
IP packet
SRC 192.168.10.10
DST 192.168.20.20
remains an IP packet between those endpoints, while the Ethernet headers change as the packet crosses the router.
That distinction between Layer 2 adjacency and Layer 3 end-to-end addressing is foundational networking knowledge.
Inspect ARP on VLAN 10
Run:
sudo ip netns exec ns10 ip neigh
You should expect ns10 to know its gateway’s MAC:
192.168.10.1 → router MAC
It does not need a direct ARP mapping for:
192.168.20.20
because that host is not on the local Layer 2 network.
Inspect the Router’s Neighbor Tables
Run:
sudo ip netns exec ns-rtr ip neigh
The router may know:
192.168.10.10 → MAC on rtr0.10
192.168.20.20 → MAC on rtr0.20
This creates a useful troubleshooting model:
ns10
IP → Gateway MAC
Router
IP → Host MAC on each VLAN
Inspect the Bridge FDB
Outside the namespaces:
bridge fdb show br br0
Remember the distinction:
ARP / neighbor table
↓
IP → MAC
while:
Bridge FDB
↓
MAC → Bridge Port
These mechanisms cooperate but solve different problems.
If the bridge/FDB concepts are unfamiliar, revisit Linux Bridge Networking with Network Namespaces and veth.
Capture the Packet on VLAN 10
Start:
sudo ip netns exec ns-rtr \
tcpdump -eni rtr0.10 icmp
Then from ns10:
sudo ip netns exec ns10 \
ping 192.168.20.20
You should see ICMP arriving at the router through VLAN 10.
Now open another terminal:
sudo ip netns exec ns-rtr \
tcpdump -eni rtr0.20 icmp
You should see the forwarded traffic leave toward VLAN 20.
This gives us direct packet evidence:
rtr0.10
↓
router
↓
rtr0.20
Capture the Tagged Trunk
The router-facing parent interface is:
rtr0
Capture:
sudo ip netns exec ns-rtr \
tcpdump -eni rtr0
You can also inspect VLAN-tagged traffic:
sudo ip netns exec ns-rtr \
tcpdump -eni rtr0 vlan
This lets you observe the difference between:
rtr0.10
rtr0.20
and the underlying trunk:
rtr0
Packet capture is often the fastest way to determine whether a problem exists at:
VLAN tagging
routing
or return traffic
rather than guessing from configuration.
Add a Firewall Between the VLANs
Successful routing does not necessarily mean every VLAN should be allowed unrestricted access to every other VLAN.
Suppose:
VLAN 10 = Application Servers
VLAN 20 = Database Servers
You might want:
VLAN 10 → VLAN 20 TCP/5432
but not:
VLAN 10 → every VLAN 20 service
This is where routing and firewall policy should be separated conceptually:
Routing
↓
Can Linux reach the network?
Firewall
↓
Should this traffic be permitted?
This distinction is crucial.
Inspect nftables Before Changing Anything
Inside the router namespace:
sudo ip netns exec ns-rtr \
nft list ruleset
Never blindly flush an existing production firewall.
For this isolated lab, we can build a controlled forwarding policy.
Example Inter-VLAN Firewall
Create a table:
sudo ip netns exec ns-rtr \
nft add table inet filter
Create a forwarding chain:
sudo ip netns exec ns-rtr \
nft 'add chain inet filter forward { type filter hook forward priority 0; policy drop; }'
Allow established and related traffic:
sudo ip netns exec ns-rtr \
nft add rule inet filter forward \
ct state established,related accept
For lab testing, permit traffic from VLAN 10 to VLAN 20:
sudo ip netns exec ns-rtr \
nft add rule inet filter forward \
iifname "rtr0.10" oifname "rtr0.20" accept
Because return traffic can match the established/related rule, the architecture becomes:
VLAN 10
|
| permitted
v
Router / Firewall
|
v
VLAN 20
VLAN 20
|
unsolicited new connection
|
X
VLAN 10
This demonstrates how routing and security policy can be combined without collapsing the VLAN boundaries.
Verify the Firewall
Run:
sudo ip netns exec ns-rtr \
nft list ruleset
Do not assume the rule exists because the command appeared to succeed.
Then test traffic again.
This gives us three independent layers:
Layer 2
VLAN isolation
↓
Layer 3
Routing
↓
Security
Firewall policy
Inter-VLAN Routing Does Not Require NAT
A common misunderstanding is:
If VLAN 10 communicates with VLAN 20, I need NAT.
No.
For our topology:
192.168.10.0/24
↓
Router
↓
192.168.20.0/24
the router is directly connected to both networks.
Ordinary routing is sufficient.
NAT becomes relevant in different situations, such as allowing private networks to reach an external network that lacks a return route.
Do not add NAT merely because traffic crosses a router.
Routing vs NAT
Keep these concepts separate.
Routing answers:
Where should this packet go?
NAT answers:
Should packet addressing be translated?
Firewall filtering answers:
Should this packet be permitted?
Therefore:
Routing
≠
NAT
≠
Firewall
A well-designed Linux network treats them as separate functions even when one Linux host performs all three.
Troubleshooting: VLAN 10 Cannot Reach Its Gateway
Test:
sudo ip netns exec ns10 \
ping 192.168.10.1
If this fails, check:
sudo ip netns exec ns10 ip -br link
Then:
sudo ip netns exec ns10 ip -br addr
Then:
bridge vlan show
Check:
veth10-br → VLAN 10
rtr-trunk → VLAN 10
Then capture:
sudo ip netns exec ns-rtr \
tcpdump -eni rtr0.10 arp
If ARP never reaches the router, do not troubleshoot IP forwarding yet.
The failure is earlier in the path.
Troubleshooting: Both Gateways Work but Inter-VLAN Traffic Fails
Suppose:
ns10 → 192.168.10.1 = works
ns20 → 192.168.20.1 = works
but:
ns10 → ns20 = fails
Layer 2 connectivity to the router is functioning.
Now check forwarding:
sudo ip netns exec ns-rtr \
sysctl net.ipv4.ip_forward
Expected:
1
Then inspect routes:
sudo ip netns exec ns-rtr ip route
Then:
sudo ip netns exec ns-rtr \
ip route get 192.168.20.20
Finally inspect:
sudo ip netns exec ns-rtr \
nft list ruleset
This narrows the problem to Layer 3 or firewall forwarding.
Troubleshooting: Traffic Reaches VLAN 20 but No Reply Returns
Capture:
sudo ip netns exec ns20 \
tcpdump -eni veth20
If the request arrives but no reply returns, inspect ns20:
sudo ip netns exec ns20 ip route
It needs a return path.
For this lab:
default via 192.168.20.1
Without that gateway, the router may deliver traffic correctly while the destination cannot return traffic to VLAN 10.
This is classic asymmetric reachability caused by a missing route.
Troubleshooting: Router Chooses the Wrong Interface
Ask Linux directly:
sudo ip netns exec ns-rtr \
ip route get 192.168.20.20
Expected:
dev rtr0.20
For VLAN 10:
sudo ip netns exec ns-rtr \
ip route get 192.168.10.10
Expected:
dev rtr0.10
If Linux chooses an unexpected path, inspect the complete routing architecture.
CorgiNetwork’s Linux Traffic Uses the Wrong Interface provides the deeper troubleshooting workflow.
Inter-VLAN Routing and Policy Routing
Our current router has a simple routing table:
192.168.10.0/24 → VLAN 10
192.168.20.0/24 → VLAN 20
Destination-based routing is sufficient.
But imagine:
VLAN 10 → ISP A
VLAN 20 → ISP B
Now the router might need:
Traffic sourced from VLAN 10
↓
Table 100
↓
ISP A
Traffic sourced from VLAN 20
↓
Table 200
↓
ISP B
That becomes a policy-routing problem.
Use CorgiNetwork’s Linux Policy Routing with ip rule and Multiple Routing Tables for that architecture.
Inter-VLAN Routing and Multiple Default Gateways
Suppose the router has:
VLAN 10
VLAN 20
ISP A
ISP B
Installing multiple default routes without understanding policy can produce incorrect return paths.
The troubleshooting chain becomes:
VLAN
↓
Router
↓
ip rule
↓
Routing table
↓
Gateway
↓
External interface
For persistent Ubuntu implementations, continue with Netplan Multiple Default Routes and Policy Routing.
Inter-VLAN Routing and rp_filter
Multi-interface Linux routers can also encounter reverse-path filtering issues.
Imagine a packet enters:
ISP B
while Linux believes the best reverse path to its source is:
ISP A
Strict reverse-path validation may interfere.
Do not immediately disable the protection.
First verify the routing design.
CorgiNetwork’s Linux rp_filter and Asymmetric Routing explains strict, loose, and disabled modes and how they interact with multi-homed routing.
Inter-VLAN Routing and ARP
ARP does not cross a router in the same way an Ethernet broadcast moves inside a VLAN.
VLAN 10 has its own Layer 2 neighbor relationships:
192.168.10.10
↓
VLAN 10
↓
192.168.10.1
VLAN 20 has:
192.168.20.1
↓
VLAN 20
↓
192.168.20.20
The router joins the two IP networks at Layer 3.
This is why inspecting:
ip neigh
inside each namespace is so valuable.
For more complex multi-interface ARP behavior, use CorgiNetwork’s Linux ARP Flux and Multiple NICs on the Same Subnet.
A Better Troubleshooting Method
Do not start with:
Ping failed.
and randomly modify routes.
Follow the packet.
For traffic:
ns10
↓
ns20
check:
1. ns10 interface
2. ns10 address
3. ns10 route
4. ns10 ARP for gateway
5. VLAN 10 bridge membership
6. router VLAN 10 interface
7. router forwarding
8. router routing decision
9. firewall forwarding
10. router VLAN 20 interface
11. VLAN 20 bridge membership
12. ns20 ARP
13. ns20 route back
14. return traffic
That sequence turns a vague networking failure into a finite investigation.
Complete Diagnostic Commands
Check namespaces:
ip netns list
Check VLAN 10:
sudo ip netns exec ns10 ip -br addr
sudo ip netns exec ns10 ip route
sudo ip netns exec ns10 ip neigh
Check VLAN 20:
sudo ip netns exec ns20 ip -br addr
sudo ip netns exec ns20 ip route
sudo ip netns exec ns20 ip neigh
Check the router:
sudo ip netns exec ns-rtr ip -br addr
sudo ip netns exec ns-rtr ip route
sudo ip netns exec ns-rtr ip neigh
Check forwarding:
sudo ip netns exec ns-rtr \
sysctl net.ipv4.ip_forward
Check bridge VLANs:
bridge vlan show
Check bridge forwarding:
bridge fdb show br br0
Check firewall:
sudo ip netns exec ns-rtr \
nft list ruleset
Finally capture:
sudo ip netns exec ns-rtr \
tcpdump -eni any
Inter-VLAN Troubleshooting Decision Tree
Host A cannot reach Host B
|
v
Can Host A reach its gateway?
/ \
NO YES
| |
v v
Check VLAN Is forwarding enabled?
and ARP / \
NO YES
| |
v v
Enable Check router
routing route
|
v
Correct interface?
/ \
NO YES
| |
v v
Fix route Check firewall
|
v
Packet reaches
destination?
/ \
NO YES
| |
v v
Capture VLAN Check
traffic return route
Production Architecture
The same principles scale beyond our laboratory.
For example:
Linux Router
|
+---------------+---------------+
| | |
VLAN 10 VLAN 20 VLAN 30
Servers Database Management
| | |
10.10.10.0/24 10.10.20.0/24 10.10.30.0/24
Firewall policy might allow:
VLAN 10 → VLAN 20
TCP 5432 only
while:
VLAN 20 → VLAN 10
new unsolicited sessions denied
and:
VLAN 30 → management services
permitted
This produces segmentation that is meaningful at both Layer 2 and Layer 3.
Do Not Use One Giant Trusted VLAN
A common small-network design is:
Everything
↓
One subnet
↓
One broadcast domain
That is easy to configure but provides little segmentation.
A more structured architecture could be:
Users
↓
VLAN 10
Servers
↓
VLAN 20
Management
↓
VLAN 30
IoT
↓
VLAN 40
Then the router/firewall determines which communication paths are actually required.
The goal is not to create VLANs simply because VLANs exist.
The goal is to create meaningful network boundaries.
When to Use a Separate Router Namespace
For laboratories, a router namespace is particularly useful because it gives the virtual router its own:
interfaces
routing table
neighbor table
firewall context
sysctl networking state
This separation makes troubleshooting easier.
Instead of mixing:
host management traffic
+
lab routing
+
VLAN gateways
inside the default namespace, the routing experiment stays isolated.
Alternative: Route on the Main Linux Host
A router namespace is not mandatory.
The host itself could own interfaces such as:
br0.10
192.168.10.1
br0.20
192.168.20.1
and route between them.
Conceptually:
VLAN 10
↓
Linux Host
↓
VLAN 20
This can be appropriate in production designs.
The namespace approach is used here because it creates a cleaner educational boundary and makes the router behave like a separate virtual network appliance.
Alternative: Separate Router Interfaces
Another design uses one router-facing interface per VLAN:
Router
/ \
| |
VLAN10 VLAN20
rather than:
Router
|
trunk
|
VLAN10 + VLAN20
Both are valid architectures.
The trunk-based model is useful because it demonstrates how one interface can carry multiple tagged VLANs.
Inter-VLAN Routing vs VRF
VLANs provide Layer 2 separation.
Routing joins different IP networks.
But some environments need separate Layer 3 routing domains as well.
Linux provides Virtual Routing and Forwarding (VRF) for this purpose.
The Linux kernel VRF documentation explains that VRF devices combined with routing rules can create separate routing domains with their own routing tables.
A useful conceptual progression is:
VLAN
↓
Layer 2 separation
Routing
↓
Communication between IP networks
VRF
↓
Layer 3 routing-domain separation
CorgiNetwork should treat VRF as its own advanced guide rather than mixing it into ordinary inter-VLAN routing.
Cleanup the Lab
First remove the namespaces:
sudo ip netns del ns10
sudo ip netns del ns20
sudo ip netns del ns-rtr
Then remove the bridge:
sudo ip link del br0
Verify:
ip netns list
and:
ip link show type bridge
The lab should now be removed.
Frequently Asked Questions
What is inter-VLAN routing?
Inter-VLAN routing is Layer 3 forwarding between IP networks associated with different VLANs.
A router or Layer 3 routing function connects the networks while the VLANs remain separate Layer 2 domains.
Can Linux route between VLANs?
Yes.
Linux contains IP routing functionality and can route between VLAN interfaces when addresses, routes, forwarding, and security policy are configured appropriately.
Does a Linux bridge route between VLANs?
Not by itself.
A bridge primarily provides Layer 2 forwarding. Routing requires a Layer 3 interface/router function connecting the VLAN networks.
Does inter-VLAN routing require NAT?
No.
If the router has routes to both networks and endpoints have correct return routes, normal routing is sufficient.
NAT is a separate function.
Do I need IP forwarding?
For an IPv4 Linux system intentionally acting as a router, IPv4 forwarding must be configured appropriately.
For this namespace lab we enable:
sysctl -w net.ipv4.ip_forward=1
inside the router namespace.
Why can a host reach its gateway but not another VLAN?
Common causes include:
IP forwarding disabled
firewall forwarding blocked
wrong route
missing return route
wrong VLAN configuration
Use packet captures on both router interfaces to determine where traffic stops.
What is router-on-a-stick?
Router-on-a-stick is an architecture where one router interface carries multiple VLANs through an 802.1Q trunk and separate VLAN subinterfaces provide Layer 3 gateways.
Our router namespace uses:
rtr0
|
+-- rtr0.10
|
+-- rtr0.20
to demonstrate this design.
How do I see the route between VLANs?
Inside the router:
ip route
For a specific destination:
ip route get DESTINATION
The ip route get interface can resolve a particular route lookup rather than simply displaying the route table.
How do I troubleshoot Linux inter-VLAN routing?
Check, in order:
interfaces
addresses
VLAN membership
gateway reachability
routes
IP forwarding
firewall
packet capture
return path
Avoid changing several layers simultaneously.
Conclusion
Linux inter-VLAN routing connects two concepts that every advanced Linux networking administrator should understand:
Layer 2 segmentation
+
Layer 3 routing
The complete architecture from this lab is:
VLAN 10
|
192.168.10.10
|
v
192.168.10.1
Linux Router
192.168.20.1
|
v
192.168.20.20
|
VLAN 20
At Layer 2:
VLAN 10
X
VLAN 20
remains isolated.
At Layer 3:
VLAN 10
↓
Router
↓
VLAN 20
communication becomes possible according to the router’s routes and firewall policy.
The most important diagnostic commands from this project are:
bridge vlan show
bridge fdb show
ip route
ip route get DESTINATION
ip neigh
sysctl net.ipv4.ip_forward
nft list ruleset
tcpdump -eni INTERFACE
More importantly, troubleshoot in the correct order.
If the endpoint cannot reach its own gateway, investigate the VLAN and Layer 2 path.
If both endpoints reach their respective gateways but cannot reach each other, investigate forwarding, routing, and firewall policy.
If traffic reaches the destination but replies disappear, investigate the return route.
If a multi-homed router chooses an unexpected uplink, move into policy routing rather than trying to solve the problem with arbitrary route metrics.
This article extends the CorgiNetwork advanced Linux networking cluster:
Linux Network Namespaces Explained establishes isolated network stacks and virtual interfaces.
Linux Bridge Networking with Network Namespaces and veth adds virtual Layer 2 switching.
Linux Bridge VLAN Filtering Explained divides that switch into separate VLAN broadcast domains.
This guide adds Layer 3 routing between those VLANs.
For more complex route selection, Linux Policy Routing with ip rule and Multiple Routing Tables adds multiple routing tables and source-based policies.
For Ubuntu multi-gateway deployments, Netplan Multiple Default Routes and Policy Routing provides the persistent configuration layer.
When reverse paths become asymmetric, Linux rp_filter and Asymmetric Routing covers reverse-path validation.
When Linux selects an unexpected interface, Linux Traffic Uses the Wrong Interface provides the diagnostic workflow.
And for multi-interface ARP anomalies, Linux ARP Flux and Multiple NICs on the Same Subnet covers the Layer 2 neighbor-resolution side.
The resulting CorgiNetwork learning path is now:
Network Namespaces
↓
veth
↓
Linux Bridge
↓
VLAN Filtering
↓
Inter-VLAN Routing
↓
Firewall Policy
↓
Policy Routing
↓
Multiple Gateways
↓
Advanced Linux Routing
The next article in this cluster should move naturally to Linux nftables Inter-VLAN Firewall: Build Stateful Firewall Rules Between VLANs, where we will turn the virtual router into a controlled firewall and build practical policies for server, management, database, and untrusted VLANs.