How To

Create VLAN Interface on Ubuntu 26.04 / 24.04 with Netplan

A VLAN interface on Ubuntu is one netplan stanza: a VLAN ID, a parent NIC and an address. The kernel adds the 802.1Q tag, the switch port has to accept it, and that is the whole trick. The parts that bite are elsewhere: a two minute boot hang, a firewall that lets ping through and blocks everything else, and links that refuse to go away after you delete them from the config.

Original content from computingforgeeks.com - post 155115

This guide creates a VLAN interface on Ubuntu three ways: a temporary one with ip link for testing, a permanent netplan VLAN on a server, and an nmcli VLAN on Ubuntu Desktop. It then routes between two VLANs on an Ubuntu box, bridges a VLAN for KVM guests and removes a VLAN cleanly. Every command below was run on Ubuntu 26.04.1 (kernel 7.0, netplan 1.2) and Ubuntu 24.04.5 (kernel 6.8, netplan 1.1.2) in October 2026.

How the lab fits together

Three Ubuntu hosts share one trunk. Each has a second NIC, ens19, that carries tagged frames only. web01 lives in VLAN 10, db01 in VLAN 20, and gw01 has a leg in both and routes between them.

Ubuntu VLAN trunk architecture diagram: web01 in VLAN 10 and db01 in VLAN 20 tag frames on ens19 toward a trunk switch port, which carries tags 10 and 20 to the gw01 router on a stick

In the lab, the trunk is a Linux bridge on Proxmox with no VLAN filtering, so it passes every tag between the VMs. On real hardware it is a managed switch port set to trunk (tagged) mode for the VLANs the server needs. If the port is an access port, tagged frames from Ubuntu are dropped and nothing in this guide will work. The switch side of VLANs and trunks covers that end.

HostUbuntuVLAN interfaceAddressRole
gw0126.04vlan10, vlan2010.10.10.1, 10.20.20.1Gateway for both VLANs
web0126.04vlan1010.10.10.10/24Server in VLAN 10
db0124.04vlan2010.20.20.10/24Server in VLAN 20

Check the parent interface

Find the NIC that is plugged into the trunk port. Brief mode keeps it to one line per interface:

ip -br link

On web01 the trunk NIC is ens19. Yours is likely enp1s0, eno1 or similar:

lo               UNKNOWN        00:00:00:00:00:00 <LOOPBACK,UP,LOWER_UP>
eth0             UP             bc:24:11:9f:69:92 <BROADCAST,MULTICAST,UP,LOWER_UP>
ens19            UP             bc:24:11:31:6d:49 <BROADCAST,MULTICAST,UP,LOWER_UP> 

Skip the vlan package that older guides install. It ships the deprecated vconfig tool plus ifupdown hooks, and neither netplan nor ip needs any of it. The 8021q kernel module is not loaded on a fresh install, but the kernel loads it on its own the first time a VLAN link is created. Nothing to modprobe.

One warning before you touch the network over SSH. If the VLAN goes on the NIC that carries your SSH session, a wrong VLAN ID or a port that is not trunked will cut you off. Keep a console open (IPMI, Proxmox, cloud serial console) until the new config is proven.

Create a temporary VLAN interface with ip

A runtime VLAN is the fastest way to prove the switch port really carries the tag. It lives until reboot and leaves no files behind. Set the parent NIC, VLAN ID and address once:

export NIC="ens19"
export VID="10"
export VIP="10.10.10.10/24"

Create the tagged link, give it the address and bring it up:

sudo ip link add link "${NIC}" name "${NIC}.${VID}" type vlan id "${VID}"
sudo ip addr add "${VIP}" dev "${NIC}.${VID}"
sudo ip link set dev "${NIC}.${VID}" up

The detailed link view proves the kernel treats it as an 802.1Q interface with the right ID:

ip -d link show "${NIC}.${VID}" | grep -o "vlan protocol 802.1Q id [0-9]*"

Expected output:

vlan protocol 802.1Q id 10

Ping something that already lives in that VLAN, usually its gateway. In the lab that is gw01 at 10.10.10.1, configured as shown in the inter-VLAN routing section further down. On your network it is the existing switch or firewall interface for that VLAN:

ping -c 3 10.10.10.1

Replies mean the tag made it across the trunk and back:

PING 10.10.10.1 (10.10.10.1) 56(84) bytes of data.
64 bytes from 10.10.10.1: icmp_seq=1 ttl=64 time=0.648 ms
64 bytes from 10.10.10.1: icmp_seq=2 ttl=64 time=0.238 ms
64 bytes from 10.10.10.1: icmp_seq=3 ttl=64 time=0.333 ms

--- 10.10.10.1 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2037ms

A wrong VLAN ID gives no error at all. We moved the same address onto a VLAN 11 link while the gateway stayed on VLAN 10, and the result was plain 100% packet loss with nothing in the logs. When ping fails silently, check the ID on both ends before anything else.

Delete the test link before you make the VLAN permanent, so netplan starts from a clean slate:

sudo ip link delete "${NIC}.${VID}"

Make the VLAN permanent with netplan

Ubuntu Server renders netplan into systemd-networkd files, so a netplan VLAN survives reboots with no extra service. Put VLANs in their own file next to 50-cloud-init.yaml instead of editing that one, because cloud-init can rewrite it:

sudo vim /etc/netplan/60-vlans.yaml

This is the exact file from web01. Change ens19, the ID, the address and the route to match your network:

network:
  version: 2
  ethernets:
    ens19:
      dhcp4: false
      dhcp6: false
      optional: true
  vlans:
    vlan10:
      id: 10
      link: ens19
      addresses:
        - 10.10.10.10/24
      routes:
        - to: 10.20.20.0/24
          via: 10.10.10.1

The parent must appear under ethernets, even with no address. Leave it out and netplan generate stops with Error in network definition: vlan77: interface 'ens19' is not defined. The interface name (vlan10 here) is free text up to 15 characters. The route sends the subnet of the other VLAN through the VLAN 10 gateway while the default route stays on the main NIC. Do not drop optional: true: the next section shows the boot delay you get without it.

Lock the file down, then generate and apply:

sudo chmod 600 /etc/netplan/60-vlans.yaml
sudo netplan generate
sudo netplan apply

Skip the chmod and every netplan command prints this, several times per apply:

** (configure:28034): WARNING **: 20:53:04.155: Permissions for /etc/netplan/60-vlans.yaml are too open. Netplan configuration should NOT be accessible by others.

The config still applies, but the file may hold Wi-Fi keys or other secrets later, so 600 is the right mode. On a remote box where the VLAN carries your session, run sudo netplan try instead of apply. It applies the change and rolls it back after 120 seconds unless you press Enter, which saves you from a bad VLAN ID.

Check the result as networkd sees it:

ip -br addr show type vlan
networkctl list

The VLAN should be routable and configured. The parent shows degraded because it has only an IPv6 link-local address, which is expected for a trunk-only NIC:

vlan10@ens19     UP             10.10.10.10/24 fe80::be24:11ff:fe31:6d49/64
IDX LINK   TYPE     OPERATIONAL SETUP
  1 lo     loopback carrier     unmanaged
  2 eth0   ether    routable    configured
  3 ens19  ether    degraded    configuring
  6 vlan10 vlan     routable    configured

4 links listed.

Netplan wrote one .netdev and one .network file per VLAN under /run/systemd/network/. Those are generated on every boot from your YAML, so never edit them directly.

Why “optional: true” matters: the 2 minute boot hang

Leave optional: true off the parent and the VLANs still come up, but every reboot takes more than two minutes on both releases. With the parent not optional, systemd-analyze blame names the culprit:

2min 45ms systemd-networkd-wait-online.service

sudo journalctl -b -u systemd-networkd-wait-online shows the service giving up exactly two minutes after it started:

Oct 07 21:14:10 gw01 systemd[1]: Starting systemd-networkd-wait-online.service - Wait for Network to be Online...
Oct 07 21:16:10 gw01 systemd-networkd-wait-online[885]: Timeout occurred while waiting for network connectivity.
Oct 07 21:16:10 gw01 systemd[1]: systemd-networkd-wait-online.service: Main process exited, code=exited, status=1/FAILURE
Oct 07 21:16:10 gw01 systemd[1]: systemd-networkd-wait-online.service: Failed with result 'exit-code'.
Oct 07 21:16:10 gw01 systemd[1]: Failed to start systemd-networkd-wait-online.service - Wait for Network to be Online.

The parent NIC has no address, so networkd leaves it in the configuring state forever, and wait-online waits for every required link until it times out. Marking the parent optional tells networkd not to wait for it. The netplan YAML reference documents the key. Measured on the same VMs:

HostWithout optional: trueWith optional: true
gw01 (26.04, two VLANs)2 min 10.2 s11.4 s
web01 (26.04)2 min 10.1 s10.1 s
db01 (24.04)2 min 8.1 s11.3 s

Route between VLANs on Ubuntu

Hosts in different VLANs cannot reach each other until something routes between them. Usually that is a layer 3 switch or a firewall. An Ubuntu box with one trunk NIC can do the same job as a router on a stick, the Linux version of the Cisco router-on-a-stick setup. gw01 gets one VLAN interface per network, and each address becomes the gateway for that VLAN:

sudo vim /etc/netplan/60-vlans.yaml

Two VLANs on the same parent, no routes needed because both subnets are directly connected:

network:
  version: 2
  ethernets:
    ens19:
      dhcp4: false
      dhcp6: false
      optional: true
  vlans:
    vlan10:
      id: 10
      link: ens19
      addresses:
        - 10.10.10.1/24
    vlan20:
      id: 20
      link: ens19
      addresses:
        - 10.20.20.1/24

Apply it the same way. IP forwarding is off by default on Ubuntu (net.ipv4.ip_forward = 0), and with it off, a ping from web01 to db01 got 100% loss. Turn it on persistently:

echo "net.ipv4.ip_forward = 1" | sudo tee /etc/sysctl.d/99-vlan-routing.conf
sudo sysctl --system

From web01, trace the path to db01:

tracepath -n 10.20.20.10

Two hops, with the VLAN 10 gateway first:

 1?: [LOCALHOST]                      pmtu 1500
 1:  10.10.10.1                                            0.382ms
 1:  10.10.10.1                                            0.320ms
 2:  10.20.20.10                                           0.669ms reached
     Resume: pmtu 1500 hops 2 back 2 

A capture on gw01’s trunk NIC shows the routing at layer 2. The request arrives tagged VLAN 10 and leaves tagged VLAN 20, and the reply makes the same trip back:

tcpdump on Ubuntu showing 802.1Q VLAN 10 and VLAN 20 tags as gw01 routes ICMP between the two VLANs

Run the same capture on your own trunk NIC whenever you doubt the tags:

sudo tcpdump -i ens19 -e -nn -q -c 4 "vlan and icmp"

If it shows no vlan lines at all, the frames never reached the box.

UFW lets ping through but blocks TCP between VLANs

This one cost us a few minutes. With UFW enabled on gw01 (default policy deny (routed)), ping between the VLANs still worked, so routing looked fine. SSH from web01 to db01 timed out:

nc: connect to 10.20.20.10 port 22 (tcp) timed out: Operation now in progress

Ping gets through because /etc/ufw/before.rules accepts forwarded ICMP echo requests before your policy applies. TCP hits the routed deny, which the kernel log shows:

[UFW BLOCK] IN=vlan10 OUT=vlan20 ... SRC=10.10.10.10 DST=10.20.20.10 LEN=60 TOS=0x00 PREC=0x00 TTL=63 ID=9435 DF PROTO=TCP SPT=57220 DPT=22 WINDOW=64240 RES=0x00 SYN URGP=0 

Allow forwarding between the two VLAN interfaces explicitly:

sudo ufw route allow in on vlan10 out on vlan20
sudo ufw route allow in on vlan20 out on vlan10

After that, nc -zv -w 3 10.20.20.10 22 from web01 printed Connection to 10.20.20.10 22 port [tcp/ssh] succeeded!. In production, narrow these rules to the ports each VLAN actually needs (... out on vlan20 to any port 5432 proto tcp) rather than opening everything. The UFW firewall guide covers the rule syntax.

Put the default gateway on the VLAN

When the VLAN is the main network of the server, it carries the default route and the DNS servers. The via address must be a router that already reaches the internet, normally the L3 switch or firewall for that VLAN. In the lab, gw01 played that role with a masquerade rule on its uplink. A reboot dropped that rule. db01 kept its default route and lost the internet.

Netplan expects a single default route per address family unless you add metrics or routing policy, so remove the old one first. On cloud images it lives in 50-cloud-init.yaml. cloud-init generated that file and can regenerate it, so stop cloud-init from managing the network before you edit it:

echo "network: {config: disabled}" | sudo tee /etc/cloud/cloud.cfg.d/99-disable-network-config.cfg

Then delete the routes entry for default from /etc/netplan/50-cloud-init.yaml. If that interface uses dhcp4: true instead, there is no static route to delete; add dhcp4-overrides: {use-routes: false} under it so DHCP stops installing the default route. The static-route edit on db01 survived a reboot with that file in place. Moving the default route can drop SSH sessions from other subnets. Work from a console or use netplan try for this change. This is the VLAN file from db01:

sudo vim /etc/netplan/60-vlans.yaml

The VLAN gets the default route via its gateway, plus resolvers:

network:
  version: 2
  ethernets:
    ens19:
      dhcp4: false
      dhcp6: false
      optional: true
  vlans:
    vlan20:
      id: 20
      link: ens19
      addresses:
        - 10.20.20.10/24
      routes:
        - to: default
          via: 10.20.20.1
      nameservers:
        addresses:
          - 1.1.1.1
          - 9.9.9.9

Set chmod 600 on the file, run sudo netplan try and press Enter while the session still answers. Then confirm the route and DNS landed on the VLAN:

ip route get 1.1.1.1
resolvectl dns vlan20

Outbound traffic now leaves through vlan20, and systemd-resolved uses the two resolvers from the file:

1.1.1.1 via 10.20.20.1 dev vlan20 src 10.20.20.10 uid 1000
    cache
Link 4 (vlan20): 1.1.1.1 9.9.9.9

The same checks from a fresh session on db01, with the tag ID and a ping to the internet through the VLAN gateway:

Ubuntu 24.04 netplan VLAN interface vlan20 with 802.1Q id 20, networkctl status and default route via 10.20.20.1

For a plain untagged static address with no VLAN involved, the netplan static IP guide has the simpler file.

Bridge a VLAN for KVM virtual machines

To put VMs on a VLAN, attach the VLAN interface to a bridge and move the host address to the bridge. On web01 the VLAN 10 address moved to br10:

sudo vim /etc/netplan/60-vlans.yaml

The VLAN keeps no address of its own. The bridge holds the address and route:

network:
  version: 2
  ethernets:
    ens19:
      dhcp4: false
      dhcp6: false
      optional: true
  vlans:
    vlan10:
      id: 10
      link: ens19
  bridges:
    br10:
      interfaces:
        - vlan10
      addresses:
        - 10.10.10.10/24
      routes:
        - to: 10.20.20.0/24
          via: 10.10.10.1
      parameters:
        stp: false
        forward-delay: 0

Use netplan apply for this one. netplan try refuses any config with bridge parameters:

br10: reverting custom parameters for bridges and bonds is not supported

Please carefully review the configuration and use 'netplan apply' directly.

After apply, bridge link show lists the VLAN as a forwarding port of the bridge. It stayed that way after a reboot (8.6 s boot):

5: vlan10@ens19: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 master br10 state forwarding priority 32 cost 100 

A guest attached to br10 sends untagged frames, and the host adds the VLAN 10 tag on the way out of ens19. One bridge per VLAN scales fine for a handful of networks. The Linux bridge on VLAN with netplan guide goes further with several VLAN bridges, and the KVM bridged network setup wires the bridge into libvirt.

Add a VLAN with nmcli on NetworkManager hosts

Ubuntu Desktop hands every interface to NetworkManager, so nmcli is the natural tool there. One command creates and stores the VLAN connection. We ran it on Ubuntu 24.04 with NetworkManager as the netplan renderer, which is how Desktop is set up:

sudo nmcli connection add type vlan con-name vlan30 ifname vlan30 dev ens19 id 30 ipv4.method manual ipv4.addresses 10.30.30.10/24 ipv6.method disabled
sudo nmcli connection up vlan30

Running the same command on Ubuntu Server, after installing network-manager, creates the connection but fails to activate it:

Error: Connection activation failed: Activation failed because the device is unmanaged

Ubuntu ships /usr/lib/NetworkManager/conf.d/10-globally-managed-devices.conf with unmanaged-devices=*,except:type:wifi,..., so NetworkManager ignores wired NICs unless netplan hands them over. Desktop installs do that with a one-line renderer file. On a server, stick with the netplan file above. The same file can switch the whole host to NetworkManager. Do that from a console, not SSH. Right after netplan apply, nmcli answered Error: NetworkManager is not running. while the service restarted.

sudo vim /etc/netplan/01-network-manager-all.yaml

The content is the same as on a desktop install:

network:
  version: 2
  renderer: NetworkManager

After chmod 600 and netplan apply, netplan writes an empty override to /run/NetworkManager/conf.d/, the devices become managed, and nmcli connection up vlan30 returned Connection successfully activated.

On Ubuntu 24.04, NetworkManager does not keep the connection in its own keyfile directory. It writes it back into netplan as /etc/netplan/90-NM-<uuid>.yaml, so ls /etc/netplan/ shows nmcli-made VLANs too:

network:
  version: 2
  vlans:
    vlan30:
      renderer: NetworkManager
      addresses:
      - "10.30.30.10/24"
      id: 30
      link: "ens19"
      networkmanager:
        uuid: "4c1667ba-50e3-4cc8-b26c-2a5bcf032b8f"
        name: "vlan30"
        passthrough:
          ethernet._: ""
          vlan.flags: "1"
          ipv6.addr-gen-mode: "default"
          ipv6.method: "disabled"
          ipv6.ip6-privacy: "-1"
          proxy._: ""

Change NM-managed VLANs with nmcli, not by editing that file, or the two views drift apart. Other distros use different tools for the same job: see the Debian VLAN guide for ifupdown and the RHEL and Fedora VLAN tagging guide for nmcli on that family.

Remove a VLAN interface cleanly

Deleting a VLAN from the YAML and running netplan apply does not delete the interface. We added a vlan99 stanza, applied, removed it and applied again, then checked both views:

ip -br addr show type vlan
networkctl list | grep vlan99

The address went away, but the link stayed up and networkd marked it unmanaged:

vlan10@ens19     UP             10.10.10.10/24 fe80::be24:11ff:fe31:6d49/64
vlan99@ens19     UP             fe80::be24:11ff:fe31:6d49/64
  5 vlan99 vlan     carrier     unmanaged

netplan apply creates virtual devices but does not remove them. A reboot clears the leftover, or delete it by hand right after the config change:

sudo ip link delete vlan99

nmcli behaves the same way. sudo nmcli connection delete vlan30 removed the profile and its 90-NM-*.yaml file, but vlan30@ens19 was still listed by ip -br link show type vlan, so remove it with the same ip link delete. Finish by confirming only the VLANs you want are left:

ip -br link show type vlan

A stale VLAN with no address does no harm on its own. It does confuse the next person who reads ip a and assumes the config file matches the running system, so clean it up in the same change window. If the switch side is also being retired, remove the VLAN from the allowed list on the trunk there; when you are shopping for that hardware, managed switches for a homelab lists models that handle 802.1Q trunks properly.

Keep reading

Upgrade Ubuntu 24.04 to Ubuntu 26.04 LTS (Step by Step) Ubuntu Upgrade Ubuntu 24.04 to Ubuntu 26.04 LTS (Step by Step) UFW Firewall Commands with Examples on Ubuntu 26.04 / 24.04 Security UFW Firewall Commands with Examples on Ubuntu 26.04 / 24.04 Ubuntu 26.04 vs 24.04 LTS: Should You Upgrade Now? Ubuntu Ubuntu 26.04 vs 24.04 LTS: Should You Upgrade Now? Upgrade a Single Package on Ubuntu / Debian with apt Debian Upgrade a Single Package on Ubuntu / Debian with apt APT Cheat Sheet: apt and apt-get Commands for Ubuntu/Debian Cheat Sheets APT Cheat Sheet: apt and apt-get Commands for Ubuntu/Debian Configure Static IP on Ubuntu 26.04 LTS with Netplan Networking Configure Static IP on Ubuntu 26.04 LTS with Netplan

3 thoughts on “Create VLAN Interface on Ubuntu 26.04 / 24.04 with Netplan”

  1. This information is out of date. A fresh install of ubuntu 24.10 is not using the /etc/network/interfaces file to bring up networking anymore.

    Reply

Leave a Comment

Press ESC to close