Programming

Linux Inter-VLAN Routing with Network Namespaces Guide 2026

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.

corginetwork

Recent Posts

AI Agents as Code: Git, Versioning & CI/CD Guide

AI Agents as Code: Managing AI Agent Configurations, Prompts and Tools with Git AI agents…

1 week ago

Data Center Power Distribution Architecture Guide

Data Center Power Distribution Architecture: UPS, PDU, Redundancy and Rack Power Explained Data center power…

1 week ago

DNSSEC Troubleshooting: How to Fix SERVFAIL, DS, DNSKEY and RRSIG Errors

DNSSEC Troubleshooting: Fix SERVFAIL & DNSKEY Errors DNSSEC Troubleshooting: How to Fix SERVFAIL, DS, DNSKEY…

2 weeks ago

Terraform State Management: Remote State, Locking & Backends

Terraform State Management Explained: Remote State, Locking, Backends and Best Practices Terraform makes infrastructure reproducible…

2 weeks ago

RTO vs RPO Explained: Disaster Recovery Objectives & Examples

RTO vs RPO Explained: Disaster Recovery Objectives, Examples and Planning A disaster recovery plan is…

2 weeks ago

Network Intrusion Detection: NIDS, Sensors & Traffic Analysis

Network Intrusion Detection Explained: NIDS, Sensors, Signatures and Traffic Analysis A firewall can decide whether…

2 weeks ago