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(withoutadd) replaces the entire allowed list with only VLAN 300, instantly breaking all other VLANs on that trunk. Always useswitchport trunk allowed vlan add 300. This single missing keyword has caused countless production outages.