Configuring 802.1Q VLANs on Linux: Setting Up Sub-interfaces and Trunking Multiple VLANs on a Single Physical NIC

Network tutorial - IT technology blog
Network tutorial - IT technology blog

Here’s a situation I’ve run into more than a few times while managing the network for a 50-person office: the managed switch had VLANs configured per department — accounting on VLAN 10, IT on VLAN 20, servers on VLAN 30 — but the Linux gateway server had only one physical NIC connected to the switch. How do you get that server to see all three VLANs at once without adding more NICs?

VLAN 802.1Q sub-interfaces were designed to solve exactly this problem. Instead of needing N NICs for N VLANs, all you need is one trunk port on the switch and the right configuration on Linux.

The Real Problem: Why One NIC Isn’t Enough the Traditional Way

Most sysadmins new to network management think in terms of 1 VLAN = 1 NIC. That approach works, but it doesn’t scale — servers typically have only 2–4 NIC ports, while a production environment can easily require 5–10 or more VLANs.

Managed switches support the trunk port mechanism: rather than passing only one VLAN (like an access port), a trunk port tags each Ethernet frame with a VLAN ID according to the IEEE 802.1Q standard. Linux receives these tagged frames, reads the VLAN ID, and delivers traffic to the appropriate sub-interface.

In practice, I use this approach for:

Core Concepts Before You Start Configuring

How 802.1Q Frame Tagging Works

The IEEE 802.1Q standard inserts a 4-byte header into the Ethernet frame, containing the VLAN ID (12 bits, range 1–4094). An untagged frame belongs to the native VLAN; a frame with the tag added is called tagged.

Switch access ports strip the tag before sending frames to end devices (PCs, printers, etc.). Trunk ports preserve the tag when forwarding — this is the port type you need to connect to your Linux server.

Sub-interfaces on Linux

Linux creates virtual interfaces like enp3s0.10 attached to the physical interface enp3s0 — think of each VLAN as a separate “channel” on the same cable. The 8021q kernel module handles all the tagging and untagging: sending a packet through enp3s0.10 causes the kernel to add a VLAN 10 tag to the frame; receiving a frame tagged with 10 on enp3s0 causes the kernel to deliver it to enp3s0.10.

Hands-On: Configuring VLAN Sub-interfaces Step by Step

Step 1: Check the 8021q Kernel Module

# Check if the module is already loaded
lsmod | grep 8021q

# If no output, load the module manually
sudo modprobe 8021q

# To load automatically at boot
echo "8021q" | sudo tee /etc/modules-load.d/8021q.conf

Ubuntu 20.04+, Debian 11+, and Rocky Linux 8+ all ship this module built into the kernel — no extra package installation required.

Step 2: Identify the Physical Interface and Prepare

# List all existing interfaces
ip link show

# Example output:
# 2: enp3s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 ...
#     link/ether 00:1a:2b:3c:4d:5e brd ff:ff:ff:ff:ff:ff

Assume the physical interface is enp3s0, connected to the switch’s trunk port carrying VLANs: 10 (accounting), 20 (IT), 30 (servers).

Important note: The physical interface must be in UP state but have no IP address. IPs should be assigned to sub-interfaces, not the physical interface itself. I made this mistake early on — assigning IPs to both resulted in packet routing confusion that was a nightmare to debug.

# Bring up the physical interface without assigning an IP
sudo ip link set enp3s0 up

Step 3: Create VLAN Sub-interfaces (Temporary Configuration for Testing)

# Create sub-interface for VLAN 10 and assign IP
sudo ip link add link enp3s0 name enp3s0.10 type vlan id 10
sudo ip addr add 192.168.10.1/24 dev enp3s0.10
sudo ip link set enp3s0.10 up

# VLAN 20
sudo ip link add link enp3s0 name enp3s0.20 type vlan id 20
sudo ip addr add 192.168.20.1/24 dev enp3s0.20
sudo ip link set enp3s0.20 up

# VLAN 30
sudo ip link add link enp3s0 name enp3s0.30 type vlan id 30
sudo ip addr add 192.168.30.1/24 dev enp3s0.30
sudo ip link set enp3s0.30 up
# Verify immediately
ip addr show | grep -A3 "enp3s0\."

# View VLAN details as recognized by the kernel
cat /proc/net/vlan/config

The output of /proc/net/vlan/config will look like this:

VLAN Dev name    | VLAN ID
Name-Type: VLAN_NAME_TYPE_RAW_PLUS_VID_NO_PAD
enp3s0.10      | 10  | enp3s0
enp3s0.20      | 20  | enp3s0
enp3s0.30      | 30  | enp3s0

Step 4: Persistent Configuration with Netplan (Ubuntu 20.04+)

The ip commands above are temporary — a reboot wipes them. For persistence, create a Netplan file:

# /etc/netplan/01-vlan.yaml
network:
  version: 2
  ethernets:
    enp3s0:
      dhcp4: false
      dhcp6: false
  vlans:
    enp3s0.10:
      id: 10
      link: enp3s0
      addresses: [192.168.10.1/24]
    enp3s0.20:
      id: 20
      link: enp3s0
      addresses: [192.168.20.1/24]
    enp3s0.30:
      id: 30
      link: enp3s0
      addresses: [192.168.30.1/24]
# Apply configuration
sudo netplan apply

# Verify
ip link show type vlan

Step 5: Persistent Configuration with NetworkManager (RHEL/CentOS/Rocky)

# Create VLAN connections using nmcli
nmcli connection add type vlan con-name vlan10 dev enp3s0 id 10 \
    ip4 192.168.10.1/24

nmcli connection add type vlan con-name vlan20 dev enp3s0 id 20 \
    ip4 192.168.20.1/24

nmcli connection add type vlan con-name vlan30 dev enp3s0 id 30 \
    ip4 192.168.30.1/24

# Activate
nmcli connection up vlan10
nmcli connection up vlan20
nmcli connection up vlan30

Step 6: Enable IP Forwarding for Inter-VLAN Routing

If this Linux server acts as a gateway — routing traffic between VLANs — you need to enable IP forwarding:

# Persistent: append to /etc/sysctl.conf
echo "net.ipv4.ip_forward = 1" | sudo tee -a /etc/sysctl.conf
sudo sysctl -p

# Verify
sysctl net.ipv4.ip_forward
# net.ipv4.ip_forward = 1

Step 7: Verify and Troubleshoot

# Ping the gateway from a machine in VLAN 10
ping 192.168.10.1

# Capture packets to verify 802.1Q tagged frames
# The -e flag shows the Ethernet header; you'll see "vlan 10" in the output
sudo tcpdump -i enp3s0 -e vlan

# Filter by specific VLAN ID
sudo tcpdump -i enp3s0 vlan 10

Sub-interface not receiving packets? The most common cause is the switch not being configured as a trunk. Check the switch configuration:

# Cisco IOS — ensure the port connected to the server is a trunk
# switchport mode trunk
# switchport trunk allowed vlan 10,20,30

# HP/Aruba ProCurve
# vlan 10,20,30 tagged A1

# Mikrotik (RouterOS)
# /interface bridge vlan add bridge=bridge1 vlan-ids=10,20,30 tagged=ether1

Lessons Learned from Managing a Real Datacenter

The issue I see newcomers stumble on most often is MTU. The 802.1Q standard adds 4 bytes to the frame header. If the switch or uplink isn’t configured for jumbo frames, packets ≥ 1497 bytes will get fragmented with no clear error message — extremely difficult to debug. Check and adjust MTU if needed:

# Sub-interface MTU defaults to match the physical interface (1500)
ip link show enp3s0.10

# If you encounter issues with large frames, try reducing the MTU
sudo ip link set enp3s0.10 mtu 1496

# With Netplan, add mtu to each vlan entry:
# enp3s0.10:
#   id: 10
#   link: enp3s0
#   mtu: 1496
#   addresses: [192.168.10.1/24]

At the small datacenter I manage, this configuration runs stably across dozens of VLANs on a single KVM host interface — management, storage, production, and DMZ VLANs. No extra NICs required, which saves PCIe slots and significantly reduces cabling.

One final note: naming sub-interfaces using the format <physical>.<vlan-id> (e.g., enp3s0.10) is the most widely used and readable convention. You can use arbitrary names like vlan-accounting, but when you’re debugging at 2 AM, enp3s0.10 tells the whole story at a glance.

Conclusion

Looking back, this setup is simpler than most people expect: no extra hardware, no exotic drivers, native kernel support through the 8021q module. The key things to remember: leave the physical interface without an IP, create sub-interfaces with the correct VLAN IDs, and make sure the switch side is a trunk port allowing exactly those VLANs.

As the number of VLANs grows, managing the configuration through Netplan or NetworkManager becomes much easier to maintain and audit than hand-rolled scripts. The natural next step is adding nftables to control inter-VLAN traffic — especially important when production and DMZ share the same trunk NIC.

Share: