Skip to content

VLANs - Street-Level Ops

Real-world VLAN diagnosis and management workflows for production environments.

Task: Create a VLAN Interface on Linux

# Load the 802.1Q kernel module
$ modprobe 8021q
$ lsmod | grep 8021q
8021q    36864  0

# Create VLAN 100 on eth0
$ ip link add link eth0 name eth0.100 type vlan id 100
$ ip addr add 10.100.0.5/24 dev eth0.100
$ ip link set eth0.100 up

# Verify
$ ip -d link show eth0.100
    vlan protocol 802.1Q id 100 <REORDER_HDR>

$ cat /proc/net/vlan/eth0.100
eth0.100  VID: 100  REORDER_HDR: 1  dev->priv_flags: 1
         total frames received        14523
         total bytes received       8721840

Remember: VLAN setup mnemonic: M-C-A-U — Module (load 8021q), Create (ip link add), Address (ip addr add), Up (ip link set up). Miss any step and the interface silently does nothing.

Task: Troubleshoot "VLAN Interface Up but No Traffic"

# VLAN interface is up with an IP but cannot reach anything
$ ip link show eth0.100
    eth0.100@eth0: <BROADCAST,MULTICAST,UP> mtu 1500 state UP

# Step 1: Is the parent interface up?
$ ip link show eth0
    eth0: <BROADCAST,MULTICAST,UP> state UP
# Parent is UP — good.

# Step 2: Is 8021q module loaded?
$ lsmod | grep 8021q
8021q    36864  0
# Loaded — good.

# Step 3: Is the switch port a trunk allowing VLAN 100?
# Capture on the physical interface to see tagged frames
$ tcpdump -eni eth0 'vlan 100' -c 5
# No output = switch is not sending VLAN 100 tagged frames
# The switch port is likely in access mode or VLAN 100 is not in the allowed list

# Fix on the switch:
# Switch(config-if)# switchport mode trunk
# Switch(config-if)# switchport trunk allowed vlan add 100

Debug clue: If tcpdump -eni eth0 'vlan 100' shows nothing, the problem is almost always on the switch side: either the port is in access mode, or VLAN 100 is not in the allowed trunk list. Linux VLAN configuration is rarely the issue — it is the switch that controls what tagged frames reach the host.

Task: Capture Tagged Traffic for a Specific VLAN

# Capture on the PHYSICAL interface to see 802.1Q tags
$ tcpdump -eni eth0 vlan -c 10
14:30:01.001 aa:bb:cc:dd:ee:ff > ff:ff:ff:ff:ff:ff, ethertype 802.1Q (0x8100),
  vlan 100, p 0, ethertype ARP, Request who-has 10.100.0.1

# Filter for a specific VLAN
$ tcpdump -eni eth0 'vlan 100' -c 10

# Capture on the VLAN interface to see untagged (post-strip) traffic
$ tcpdump -ni eth0.100 -c 10
# Shows normal IP traffic without VLAN tags

Task: Persistent VLAN Configuration with nmcli

# Create VLAN connection
$ nmcli con add type vlan con-name vlan100 dev eth0 id 100 \
    ipv4.addresses 10.100.0.5/24 ipv4.method manual

# Verify
$ nmcli con show vlan100 | grep -E "vlan.id|ipv4.addr"
vlan.id:        100
ipv4.addresses: 10.100.0.5/24

# Activate
$ nmcli con up vlan100

# Verify interface exists
$ ip -br addr show eth0.100
eth0.100  UP  10.100.0.5/24

Under the hood: When you capture on the physical interface (eth0), you see 802.1Q-tagged frames with VLAN IDs. When you capture on the VLAN interface (eth0.100), the kernel has already stripped the tag — you see plain IP traffic. Use the physical interface to debug tagging problems; use the VLAN interface to debug application-level traffic.

Task: Debug Native VLAN Mismatch

# Symptoms: intermittent connectivity, traffic appearing in wrong VLAN
# On Cisco switch, check for mismatch warnings
# Switch# show interfaces trunk
# Port      Native VLAN
# Gi0/1     1
# Gi0/2     100        <-- Mismatch!

# On Linux, check if untagged traffic is arriving
$ tcpdump -eni eth0 not vlan -c 10
# If you see traffic here that should be on a VLAN, native VLAN is misconfigured

# Fix: ensure both sides of the trunk agree on native VLAN
# Switch(config-if)# switchport trunk native vlan 999

Gotcha: Native VLAN mismatch is one of the sneakiest network bugs. One end of a trunk sends untagged frames on VLAN 1, the other end puts untagged frames into VLAN 100. Traffic from one VLAN silently leaks into another. Cisco switches log CDP-4-NATIVE_VLAN_MISMATCH — check for these warnings. Best practice: set native VLAN to an unused VLAN (e.g., 999) on both sides and never put anything on it.

Task: Set Up Docker Macvlan on a VLAN

# Place containers directly on VLAN 200
# Prerequisite: eth0 must be on a trunk port allowing VLAN 200

# Create VLAN interface first
$ ip link add link eth0 name eth0.200 type vlan id 200
$ ip link set eth0.200 up

# Create Docker macvlan network
$ docker network create -d macvlan \
    --subnet=10.200.0.0/24 \
    --gateway=10.200.0.1 \
    -o parent=eth0.200 \
    vlan200_net

# Run a container on the VLAN
$ docker run -d --network vlan200_net --ip 10.200.0.50 nginx

# Container is reachable from the physical VLAN 200 network
# NOTE: the host itself cannot reach macvlan containers — this is by design
$ ping -c 1 10.200.0.50
64 bytes from 10.200.0.50: icmp_seq=1 ttl=64 time=0.3 ms

Default trap: Docker macvlan isolates containers from the host by design — the host cannot ping its own macvlan containers. If you need host-to-container communication, create a separate macvlan sub-interface on the host: ip link add mac0 link eth0.200 type macvlan mode bridge && ip addr add 10.200.0.254/32 dev mac0 && ip link set mac0 up.

Task: Verify VLAN Traffic Counters

# Check per-VLAN traffic on Linux
$ ip -s link show eth0.100
    RX:  bytes packets errors dropped
    8721840   14523      0       0
    TX:  bytes packets errors dropped
    5432100    9871      0       0

# ARP resolution within the VLAN
$ ip neigh show dev eth0.100
10.100.0.1 lladdr aa:bb:cc:dd:ee:01 REACHABLE
10.100.0.10 lladdr aa:bb:cc:dd:ee:10 STALE

# If no ARP entries, hosts on this VLAN are not reachable at L2

Scale note: The 802.1Q standard supports VLAN IDs 1-4094 (12-bit field, minus 0 and 4095 which are reserved). In practice, VLANs 1 (default) and 1002-1005 (legacy token ring/FDDI) are reserved on Cisco gear. For large environments needing more than 4094 segments, look at VXLAN (24-bit VNI = ~16 million segments).

Task: Multiple VLANs on One Host

# Server needs access to web VLAN 100, database VLAN 200, management VLAN 300
$ for vid in 100 200 300; do
    ip link add link eth0 name eth0.${vid} type vlan id ${vid}
    ip link set eth0.${vid} up
done

$ ip addr add 10.100.0.5/24 dev eth0.100
$ ip addr add 10.200.0.5/24 dev eth0.200
$ ip addr add 10.30.0.5/24 dev eth0.300

# Verify all VLAN interfaces
$ ip -br link show | grep eth0
eth0       UP  aa:bb:cc:dd:ee:ff
eth0.100@eth0  UP  aa:bb:cc:dd:ee:ff
eth0.200@eth0  UP  aa:bb:cc:dd:ee:ff
eth0.300@eth0  UP  aa:bb:cc:dd:ee:ff

Task: Check Switch VLAN Configuration (Cisco)

# Verify VLAN exists in the switch database
Switch# show vlan brief
VLAN Name                 Status    Ports
---- -------------------- --------- ----------------------------
1    default              active    Gi0/5, Gi0/6
100  web-servers          active    Gi0/1, Gi0/2
200  databases            active    Gi0/3, Gi0/4

# Verify trunk settings
Switch# show interfaces Gi0/1 trunk
Port      Mode     Encapsulation  Status
Gi0/1     on       802.1q         trunking

Port      Vlans allowed on trunk
Gi0/1     100,200,300

# If VLAN 300 is missing from "allowed," add it:
# Switch(config-if)# switchport trunk allowed vlan add 300

Gotcha: On Cisco switches, switchport trunk allowed vlan 300 (without add) replaces the entire allowed list with only VLAN 300, instantly breaking all other VLANs on that trunk. Always use switchport trunk allowed vlan add 300. This single missing keyword has caused countless production outages.