Runbook: Add a VLAN
1 · Prerequisites
Confirm every item is in place before any state change.
- IEEE 802.1Q — the tagging model
- VLAN sub-interfaces — eth0.10, eth0.20, ...
- Trunks and access — what the operator controls at each end of the cable
- VLAN routing — moving packets between VLANs on the same router
- VLAN troubleshooting — finding the broken VLAN in five minutes
- VLAN security — VLAN hopping, native VLAN attacks, and the mitigations
- commit-confirm — the rollback safety net for remote changes
2 · Pre-checks
Read-only diagnostic commands. If any of these don't match expected output, stop and investigate further.
- · The parent interface is already in service and reads
u/uinshow interfaces— a VLAN cannot be added to a link that is not up - · Tagged frames for the VID are already arriving on the parent:
monitor traffic interface eth1 filter 'vlan 10'returns traffic before any configuration change - · The switch owner has confirmed in writing that the VID is allowed on this trunk port, and that the port is a trunk rather than an access port
- · Baseline captured:
show interfaces,show ip route,show configuration commandsfiltered on the parent interface - · The address this router will hold on the VLAN is confirmed free, and it is not the address another router already holds as the segment gateway
- ·
show ip routechecked for the VLAN's subnet — the new connected route installs at administrative distance 0 and displaces every other source for that prefix - · The firewall ruleset that will apply to this VLAN is named and owned. A new sub-interface arrives with no rules of its own.
- · Out-of-band console open, peer reviewer named for the
comparediff, change window end time agreed
3 · Procedure
Execute each step in order. Verify the expected output of a step before moving to the next.
- 1Confirm the frames exist before you configure anything:
monitor traffic interface eth1 filter 'vlan 10'. If nothing arrives, this is a switch change and not a router change — stop here. - 2Capture the baseline from the out-of-band console:
show interfaces,show ip route,show configuration commandsfiltered on the parent. - 3Commit 1, the sub-interface without an address:
configure,set interfaces ethernet eth1 vif 10 description 'CR-1204 VLAN 10 DATA',compare,commit. No address yet — this cannot draw traffic. - 4Confirm the kernel created the device and is demultiplexing the tag into it:
ip link show eth1.10showseth1.10@eth1up, andtcpdump -i eth1.10 -n -c 10shows untagged traffic because the kernel has already stripped the tag. - 5Commit 2, the address:
set interfaces ethernet eth1 vif 10 address '192.0.2.1/24', thencompare. Read the diff for anything touching the PARENT node — a parent-level change affects every VLAN on the trunk, not just this one. - 6Have the named reviewer read the diff, then apply with
commit-confirm 10if you reach this router over the network, orcommitfrom the console. - 7Verify the address in both views:
show interfaces ethernet eth1 vif 10andip addr show eth1.10must agree. - 8Verify the route:
show ip routeshows the new connected route oneth1.10, and the rest of the table matches the baseline. - 9Verify reachability on the VLAN itself: a host on the VLAN must reach the router's address, and the router must reach that host.
- 10Verify what the VLAN is actually for: if this router routes between VLANs, test a path from this VLAN to another one. Reaching the router proves the address; it proves nothing about routing.
- 11Apply and verify the firewall ruleset for the new VLAN before it carries production traffic.
- 12Confirm inside the timer with
confirm, thensave, then close the ticket with the post-changeshow interfaces,show ip route, and the approved diff.
4 · Verification
Confirm the procedure actually fixed the problem.
- ✓
ip link show eth1.10shows the device aseth1.10@eth1and up — the@eth1names the parent and proves the tag binding - ✓
show interfaces ethernet eth1 vif 10andip addr show eth1.10both show the configured address - ✓
tcpdump -i eth1.10 -nshows traffic, and it is untagged: the kernel strips the 802.1Q tag on the way into the sub-interface - ✓
show ip routecontains the connected route for the VLAN subnet oneth1.10, and differs from the baseline by that route alone - ✓A host on the VLAN reaches the router address, and the router reaches that host
- ✓An inter-VLAN path through this router is tested end to end, if inter-VLAN routing is what the VLAN is for
- ✓The intended firewall ruleset is applied to this sub-interface and its effect confirmed, not assumed
- ✓
savehas run, so the sub-interface survives a reboot
5 · Rollback
If verification fails, undo the procedure in reverse order.
- ↶Before the address commit, rollback is invisible to traffic:
delete interfaces ethernet eth1 vif 10,commit - ↶After the address commit and before
confirm, do nothing — the commit-confirm timer restores the previous configuration by itself - ↶After
confirm:delete interfaces ethernet eth1 vif 10,compare,commit,save. This removes the kernel device, the address and the connected route in one operation. - ↶Anything using this router as its gateway on the VLAN loses that gateway the instant the delete is committed — confirm who moved onto it during the window before you revert
- ↶If you also changed a PARENT-level node in the same window (MTU,
native-vlan), a whole-treerollback 1reverts those too, and reverting a parent MTU resets the parent and drops every VLAN on the trunk. Readcomparebefore committing any rollback. - ↶Verify the reverted state against the baseline captured in step 2, not against memory
6 · Escalation
When the runbook isn't enough, contact:
- · Escalate to the switch owner when no tagged frames for the VID arrive on the parent, when frames arrive with a different VID, or when every frame is untagged — none of these is fixable on the router
- · Escalate to the switch owner rather than working around a native-VLAN mismatch: untagged frames landing in different VLANs at each end is a trunk configuration defect, not a router one
- · Escalate to network engineering before committing the address if
show ip routealready carries the VLAN subnet from another source - · Escalate to network engineering if this router is one of a redundant pair on the VLAN — the virtual address is a separate change with its own failover behaviour and must not be improvised here
- · Escalate to security if the trunk still carries VLAN 1 as its native VLAN, or if unused VIDs are allowed on the port. Both are the double-tagging attack surface and both are switch-side changes.
- · Hold rather than force: commit the sub-interface with
disableset, hand the blocking item to its owner with an agreed end time, and record the router as unchanged
Adding a VLAN to a VyOS router is half a change. The router half is
two set lines and takes a minute. The half that determines whether
it works is on a switch, and on most estates that switch belongs to
somebody else.
That asymmetry is what this runbook is built around. The commonest outcome of a VLAN addition that “did not work” is a sub-interface that is configured correctly, is up, has the right address, and receives nothing — because the VID was never allowed on the trunk port. Every VyOS diagnostic agrees the router is fine, and the router is fine.
The good news is that the decisive check is free, takes thirty
seconds, and can be run before you touch configure. The parent
interface sees every frame on the wire, tagged or not, whether or not
a sub-interface exists for that VID. If the frames are not there
before the change, no configuration you commit will make them appear.
When to use this runbook
- A new VLAN is being added to a router already trunked to the switch: the router-on-a-stick pattern, one physical link carrying several VLANs with the router providing L3 between them.
- A router needs an L3 presence on a VLAN that already exists elsewhere in the estate.
- A VLAN that was pruned from a trunk during an incident is being restored.
When not to use it
- The physical parent is not in service yet. Bring the interface up first with the Add an Interface runbook; a VLAN on a link that is not up cannot be validated.
- This router is the access-port end for a single VLAN. Then
there is no
vifat all: the switch sends untagged frames and the address goes on the parent. That configuration is rare on a production router and is worth confirming rather than assuming. - Stacked VLANs (QinQ). The shape is the same but the validation is not, and a service-provider outer tag has different failure modes than a customer inner tag.
- Changing the native VLAN on an existing trunk. That changes how untagged frames are classified, at both ends, and there is a window in which the two ends disagree. It is its own coordinated change.
Blast radius
The new sub-interface — plus, potentially, every other VLAN on the same physical link.
The sub-interface itself is well contained: creating vif 10 does
not disturb vif 20. The risk is in the parent. Any node you change
under interfaces ethernet eth1 while you are “in there anyway” —
MTU, native-vlan, disable, a description edit that turns into a
typo — lands on the trunk as a whole. That is why the procedure asks
you to read the compare output specifically for parent-level lines.
The second exposure is the routing table. A VLAN address creates a connected route, and connected routes install at administrative distance 0: ahead of static at 1, eBGP at 20, OSPF at 110, iBGP at 200. If the VLAN’s subnet is already carried by a dynamic protocol, committing the address makes this router stop believing the protocol and start believing the wire.
Inputs
- The VLAN ID, and the subnet and address this router will hold on it.
- The parent interface, and confirmation that it is a trunk.
- The switch owner, and their written confirmation that the VID is allowed on that port.
- Whether this router is the sole gateway for the VLAN or one of a redundant pair.
- The firewall ruleset that will apply, and its owner.
- Change window, reviewer, and a working out-of-band path.
Step 1: Prove the frames are already arriving
monitor traffic interface eth1 filter 'vlan 10'Or from the underlying shell, which shows the link-layer header and is easier to read when you are not sure what you are looking at:
tcpdump -i eth1 -e -n vlan 10 -c 10Three outcomes, and each one decides the change:
- Tagged frames with your VID arrive. The switch is trunking the VLAN to this port. Proceed.
- Nothing arrives. The VID is not allowed on this trunk port, or the port is not a trunk. This is a switch change. Stop, and hand it to the switch owner with the capture attached.
- Frames arrive with a different VID, or untagged. The two ends disagree about what this link carries. Confirm the VID with the switch owner before configuring anything; picking the VID you can see is how one router quietly ends up on the wrong segment.
Step 2: Baseline, and decide the address
show interfaces
show ip route
show configuration commands | match eth1Two questions the baseline answers. Does anything already carry this subnet — and if so, is displacing it the intent, or an accident you are about to have? And is the address free: a VLAN gateway address already held by another router is the most common duplicate, because the VLAN existed elsewhere before it existed here.
If this router is joining an existing gateway pair, the sub-interface takes its own unique address and the shared virtual address is a separate change with its own failover semantics. Do not improvise it into this one.
Step 3: Commit the sub-interface, not the address
configure
set interfaces ethernet eth1 vif 10 description 'CR-1204 VLAN 10 DATA'
compare
commitA sub-interface with no address contributes no connected route and draws no traffic, so this commit cannot change forwarding. What it does give you is a real kernel device to test against.
Under the commit, VyOS creates eth1.10 as a VLAN device bound to
eth1 with VID 10. The naming matters operationally: the VyOS
configuration tree calls it eth1 vif 10, the kernel calls it
eth1.10, and roughly half of the diagnostics you will reach for
speak the kernel name.
Step 4: Confirm the kernel is demultiplexing the tag
vyos@vyos:~$ ip link show eth1.105: eth1.10@eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
link/ether 52:54:00:12:34:57 brd ff:ff:ff:ff:ff:ffIllustrative output
Then capture on the sub-interface rather than the parent:
tcpdump -i eth1.10 -n -c 10The traffic here is untagged, because the kernel has already stripped the 802.1Q header on the way in. That is the point of the check: step 1 proved the switch was sending VLAN 10; this proves the router is receiving VLAN 10 and putting it where you asked. Between those two facts there is nothing left for the address commit to surprise you with.
Step 5: Commit the address
configure
set interfaces ethernet eth1 vif 10 address '192.0.2.1/24'
compare
commit-confirm 10
exitRead the compare output twice: once for the lines you intended, and
once for anything under interfaces ethernet eth1 that is not under
vif 10. A parent-level line in this diff is the difference between
affecting one VLAN and affecting the trunk.
Point of no return. After this commit the router holds an address on the VLAN and advertises reachability for the subnet through its connected route. Hosts can start using it immediately, which is what makes the rollback a coordination problem rather than a command.
Step 6: Verify what the VLAN is for, not just that it is up
show interfaces ethernet eth1 vif 10
ip addr show eth1.10
show ip routeThe address must appear in both the VyOS view and the kernel view. An address in one and not the other is a sub-interface that is not forwarding, whatever the configuration says.
Then test the thing the VLAN exists for. A host on VLAN 10 reaching the router’s VLAN 10 address proves only that the address is live on the right segment. If this router is providing inter-VLAN routing, test a path from VLAN 10 to another VLAN and back. That is the function being commissioned, and it is the one that fails when a route or a firewall rule is missing.
Then, and only then:
configure
confirm
save
exitconfirm keeps the change in the running configuration. save
writes it to disk. A VLAN that was commissioned, verified and
confirmed but never saved works perfectly until the next reboot.
A new VLAN arrives unfiltered
A new sub-interface has no filtering of its own. In a router-on-a-stick this matters more than on a single routed interface, because separating traffic into VLANs and then applying one shared ruleset to all of them gives back exactly the separation the VLANs were for: a rule that permits something for one VLAN permits it for all of them. Per-VLAN rulesets are the pattern.
Native VLAN: real, important, and not today’s change
Untagged frames on a trunk belong to the native VLAN, which defaults to VLAN 1. Leaving it there is the surface for the double-tagging attack: a frame carrying an outer tag for the native VLAN and an inner tag for the target, where the switch strips the outer tag and forwards the frame into a VLAN the sender should not reach. The standard mitigation is to move the native VLAN to an unused ID and prune VLAN 1 from the trunk.
All of which is correct, and none of which belongs in a change whose purpose is to add one VLAN. Both ends have to move at the same time, and for as long as they disagree, untagged frames land in different VLANs at each end. Raise it with the switch owner as its own coordinated change, and record it in the ticket so it is not lost.
Rollback
configure
delete interfaces ethernet eth1 vif 10
compare
commit
saveOne commit removes the kernel device, the address and the connected route together. Before the address commit that is invisible to traffic. After it, anything that started using this router as its VLAN 10 gateway loses it at the moment of the commit — so the question to answer first is who moved during the window, not which command to type.
The trap is the whole-tree revert. If the window also contained a
parent-level change, rollback 1 takes that back too, and reverting
a parent MTU resets the trunk and interrupts every VLAN on it. Run
compare after rollback and before commit, every time; that step
is what turns rollback into a reviewed change rather than a second
incident.
Common patterns
| Symptom | Likely cause | Next check |
|---|---|---|
| Sub-interface up, capture on it silent | Switch is not trunking the VID to this port | Capture on the PARENT with a vlan filter |
| Parent shows tagged frames for another VID | The two ends disagree about the VLAN ID | Confirm the VID with the switch owner |
| Parent shows only untagged frames | The port is an access port, not a trunk | Switch owner; do not add a vif to work around it |
| Hosts reach the router, nothing beyond it | Missing route or missing firewall rule for the VLAN | show ip route, then the ruleset |
| Inter-VLAN works one direction only | Asymmetric path with a stateful device in it | Test from both ends, not just from the router |
| Small packets fine, large transfers hang | Tag overhead against the path MTU | Do-not-fragment ping sized across the VLAN |
| Whole trunk interrupted at commit | A parent-level node changed in the same commit | Read compare before committing |
| VLAN works until the router reboots | save never ran | Compare running against the boot configuration |
Escalation
Escalate rather than work around when:
- No frames for the VID arrive, frames arrive with a different VID, or all frames are untagged. Every one of these is a switch change, and configuring the router around it produces a router that is wrong in a way that looks right.
- The VLAN’s subnet is already carried by another routing source.
- This router will be one of a redundant pair on the VLAN.
- The trunk still carries VLAN 1 as its native VLAN, or allows VIDs nobody uses. That is an open attack surface and it belongs to the switch owner.
Holding is a legitimate outcome: commit the sub-interface with
disable set, name the owner of the blocking item, agree an end
time, and record the router as unchanged.