VRF configuration — set vrf name, table ids, attaching interfaces, addresses
What you'll learn
- Configure a VRF with `set vrf name` and a table id the validator will accept
- Bind an interface to a VRF and verify the enslavement at the kernel level
- Configure an address inside a VRF and confirm the connected route lands in the per-VRF table
- State why the order of `set` commands does not matter and what does control commit ordering
- Predict and recover from the four commit rejections VyOS raises around VRFs
Prerequisites
Verified against VyOS 1.5.x LTS (circinus) · VyOS 1.4.x (sagitta) — legacy · FRRouting 10.x (VyOS 1.5) · Linux kernel 6.6 LTS (VyOS 1.5 base) · strongSwan 5.9.x (IPsec) · WireGuard 1.0.x (kernel module + userspace tooling) · 2026-08-19
Configuring a VRF on VyOS 1.5 LTS is three statements:
- Create the VRF with
set vrf name VRFNAME table TABLEID. - Enslave an interface with
set interfaces ethernet SLAVEIF vrf VRFNAME. - Address the interface as normal with
set interfaces ethernet SLAVEIF address A.B.C.D/N.
What makes this worth a lesson is not the three lines. It is that the validator enforces a specific set of rules about names, table ids and references, and that a rejection at commit tells you which one you broke only if you know the list. This lesson walks the canonical configuration, shows what the commit performs at the kernel level, and works through each rejection.
The minimal VRF configuration
A single-tenant VRF on a multi-tenant edge router:
configure
# Declare the VRF and its kernel table id
set vrf name CUST-A table 1001
set vrf name CUST-A description "Customer A tenant"
# Enslave the tenant-facing interface
set interfaces ethernet eth1 vrf CUST-A
# Address the interface - the connected routes land in table 1001
set interfaces ethernet eth1 address 10.1.0.1/24
set interfaces ethernet eth1 address 2001:db8:1::1/64
set interfaces ethernet eth1 description "CUST-A LAN gateway"
# A default route that lives inside the VRF
set vrf name CUST-A protocols static route 0.0.0.0/0 next-hop 10.1.0.254
commit
save
Note that a static IPv6 address goes under the same address node as IPv4 —
that node accepts either family. The separate ipv6 node on an interface
exists for the things that are IPv6-only, such as autoconf and eui64, not
for static addressing.
The result:
- Kernel table
1001is dedicated to VRFCUST-A. eth1is enslaved to a Linux interface namedCUST-A.10.1.0.0/24and2001:db8:1::/64are connected routes in table 1001, not in table 254.10.1.0.254is the default gateway for anything inside the VRF.
flowchart TB
subgraph BEFORE["Before commit"]
E0["eth1, no master"] -.->|"lookup"| K0["table 254 (main)"]
end
subgraph AFTER["After commit"]
E1["eth1, master CUST-A"] --> V["CUST-A (type vrf, table 1001)"]
V --> T["table 1001"]
T --- R1["10.1.0.0/24 connected via eth1"]
T --- R2["0.0.0.0/0 via 10.1.0.254"]
T --- R3["2001:db8:1::/64 connected via eth1"]
end
Order of entry does not matter
There is a persistent belief that the VRF must be typed before the interface
binding. It does not. VyOS set commands build a configuration tree; the
tree has no memory of the order they were typed in. What decides the order of
operations at commit time is the priority attached to each node, and the
vrf node carries priority 11 precisely so that it runs ahead of every
interface node.
What does matter is that the VRF is present in the same candidate
configuration. set interfaces ethernet eth1 vrf CUST-A with no
corresponding set vrf name CUST-A fails verification, because the binding
references a VRF that will not exist after the commit.
What the commit performs
The vrf commit script does considerably more than one ip link add. For a
box gaining its first VRF, in order:
# 1. Restructure routing policy (see xv-01) - once, on the first VRF
ip -4 rule add pref 32765 table local
ip -4 rule del pref 0
ip -4 rule add pref 1000 l3mdev protocol kernel
ip -4 rule add pref 2000 l3mdev unreachable
# ... and the same four for -6
# 2. Create the VRF interface, named for the VRF
ip link add CUST-A type vrf table 1001
# 3. Give it loopback addresses and the description as an alias
ip address add 127.0.0.1/8 dev CUST-A
ip address add ::1/128 dev CUST-A
ip link set dev CUST-A alias "Customer A tenant"
ip link set dev CUST-A up
# 4. Register the VRF in the per-VRF conntrack zone map
nft add element inet vrf_zones ct_iface_map { "CUST-A" : 1001 }
The interface node then enslaves the port and configures the addresses:
ip link set dev eth1 master CUST-A
ip address add 10.1.0.1/24 dev eth1
ip address add 2001:db8:1::1/64 dev eth1
You do not run any of this by hand on a VyOS box — the commit owns it, and state you create outside the configuration will be reverted or contradicted at the next commit. It is written out because the failure modes later in this lesson are all “one of these steps did not happen”, and you cannot diagnose that without knowing the list.
The rules the validator enforces
Names. A VRF name becomes a Linux interface name, so the constraints are the kernel’s:
- 15 characters or fewer.
- Letters, digits,
-and_only. - Must not begin with an interface-type string.
eth,bond,br,dum,lan,eno,ens,enp,enx,gnv,ipoe,l2tp,ppp,pppoe,peth,tun,vti,vtun,vxlan,wg,wlan,wwan,lo, or a leading digit, are all rejected. - Must not be one of the reserved words:
add,all,broadcast,default,delete,dev,down,get,inet,link,mtu,type,up,vrf.
Table ids. The value must be an integer from 100 to 65535. It is
mandatory — a VRF with no table id fails to commit. 254 is rejected
explicitly because it is the main table. It must be unique across VRFs. And
it cannot be changed once the VRF interface exists, because the kernel offers
no way to re-table a live vrf device: changing a table id is a delete and a
recreate.
# Accepted
set vrf name CUST-A table 1001
set vrf name CUST-B table 1002
set vrf name MGMT table 100
# Rejected, and the reason
set vrf name eth-mgmt table 1003 # starts with an interface-type string
set vrf name default table 1004 # reserved word
set vrf name CUSTOMER-A-NORTH-1 table 1005 # 18 characters, over the 15 limit
set vrf name CUST-C table 254 # 254 is the main table
set vrf name CUST-D table 99 # below the 100-65535 window
set vrf name CUST-E table 1001 # 1001 already belongs to CUST-A
Validation: confirm the enslavement actually happened
After commit, check the VyOS view and the kernel view. They are two
different sources of truth and they can disagree.
$ show vrfName State MAC address Flags Interfaces
------ ------- ----------------- ------------------------ ------------
CUST-A up be:6f:2a:11:8c:04 noarp,master,up,lower_up eth1
CUST-B up 9a:41:d7:33:0e:b2 noarp,master,up,lower_up eth2Illustrative output
The command is show vrf for every VRF and show vrf CUST-A for one. The
VRF name is a positional value in operational mode, so there is no name
keyword — show vrf name CUST-A is a configuration-mode path, not an
operational command. show vrf CUST-A processes lists the pids running
inside the VRF, which is the quickest way to answer “is anything actually
bound in there”.
The rest of the check set:
# Kernel
ip vrf show
ip -d link show dev CUST-A
ip link show master CUST-A
ip -d link show dev eth1
ip route show table 1001
ip -6 route show table 1001
# FRR
vtysh -c 'show vrf'
vtysh -c 'show ip route vrf CUST-A'
Four things must all be true:
show vrflists the VRF with stateup.ip -d link show dev CUST-Areports the device andvrf table 1001.ip -d link show dev eth1reportsmaster CUST-Aandvrf_slave table 1001.ip route show table 1001holds the connected routes, andip route show table 254does not.
Any one of the four can be false while the others are true. Check all four.
How addresses install inside a VRF
set interfaces ethernet eth1 address 10.1.0.1/24 on an enslaved interface
installs:
- The address on
eth1, visible inip address show dev eth1exactly as it would be on any interface. - A connected route
10.1.0.0/24 dev eth1in table 1001, because the kernel derives the table from the slave’s master. - Nothing at all in table 254.
$ ip route show table 1001
10.1.0.0/24 dev eth1 proto kernel scope link src 10.1.0.1
$ ip route show table 254
default via 192.0.2.1 dev eth0
192.0.2.0/24 dev eth0 proto kernel scope link src 192.0.2.50
The absence of 10.1.0.0/24 from table 254 is the evidence that isolation is
working. If it appears there, eth1 is not enslaved, whatever the
configuration tree says.
Detaching an interface
configure
delete interfaces ethernet eth1 vrf
commit
save
The commit performs ip link set dev eth1 nomaster. The interface returns to
the default VRF and its addresses stay configured — but their connected
routes move from table 1001 to table 254, because the table follows the
master, not the address. That is a routing change to every host on the subnet
in a single commit, so it belongs in a change window even though it reads
like a one-line edit.
The related mistake is deleting the binding while leaving a per-VRF static route that pointed through the interface. The static route stays in the VRF, the interface that resolved its next hop does not, and the route goes inactive. Delete both, or move both.
Deleting a VRF
VyOS will not remove a VRF that something still references. The check runs before anything is touched, and it covers three kinds of reference:
- Member interfaces —
Cannot remove VRF "CUST-A", it still has member interfaces! - Static routes configured inside the VRF — the same message ending
it still has static routes installed! - Policy routes that name the VRF — the same message ending
it still has policy routes!
Because verification reads the candidate configuration, the fix is not to run two commits. Delete the references and the VRF together:
configure
delete interfaces ethernet eth1 vrf
delete vrf name CUST-A
commit
save
That commits cleanly. What fails is deleting only the VRF and leaving the binding behind — a rejected commit, with the running configuration untouched.
When the last VRF on the box goes away, the commit also unwinds the routing
policy changes from xv-01: local returns to priority 0 and both l3mdev
rules are removed. Check ip -4 rule show after the change, not because it
usually goes wrong, but because the global forwarding behaviour of the box
has just changed shape.
How it fails
- The commit is rejected for a name. Over 15 characters, a leading interface-type string, or a reserved word. The message names the constraint. Nothing is applied.
- The commit is rejected for a table id. Missing, outside 100-65535, equal to 254, duplicated across VRFs, or an attempt to change one that already exists. Nothing is applied; the previous configuration keeps running.
- The commit is rejected for a reference. A VRF being deleted still has interfaces, static routes or policy routes, or another VRF imports from it in BGP. Delete the reference in the same commit.
- The configuration says enslaved and the kernel does not. Almost always
out-of-band interference: an
ip link set nomasterrun by hand, or an interface recreated by something outside the configuration. The tell isip -d link show dev eth1with nomasterfield whileshow configuration commands | match eth1shows the binding. Acommitof an unchanged configuration will not fix it, because nothing changed; force the interface node to run again by deleting and re-adding the binding in one commit. - The table id collides with a policy-routing table. Both commit, both
work, and every future
ip route show table 100mixes routes from two subsystems. Nothing fails; the cost is paid during the next incident.
Rollback
# Move one interface out of a VRF, keep the VRF
configure
delete interfaces ethernet eth1 vrf
commit
save
# Remove the VRF and its references together
configure
delete interfaces ethernet eth1 vrf
delete vrf name CUST-A
commit
save
A safe rollback sequence:
compareto confirm the candidate diff reverses the forward change and removes every reference, not just the VRF.commit— a rejection here means a reference was missed, and the message names which kind.ip -d link showto confirm the VRF interface is gone and no port still reports it as a master.ip -4 rule showto confirm the rule chain matches the box’s new VRF count.saveonly after the kernel and the configuration agree.
Production discipline
Cross-course references
- The Linux course’s
V-Linux-NetConfigandXXI-Linux-NetAdvancedcoverip link add ... type vrf table N,ip link set ... master, andip vrf execfrom the host side, without the commit layer. - The OPNsense course reaches a similar isolation goal with per-VLAN interfaces and per-interface firewall rules rather than VRFs.
vyos-xv-01-vrf-conceptexplains the l3mdev model and the routing-policy rewrite that this lesson’s commit output performs.vyos-xv-03-vrf-routing-protocolsputs OSPF and BGP inside the VRF built here, and covers moving routes across the boundary deliberately.
Quiz
Knowledge check · 4 questions
Q1. An operator types `set interfaces ethernet eth1 vrf CUST-A` before `set vrf name CUST-A table 1001` in the same configuration session. What happens at commit?
Q2. `set vrf name MGMT table 100` is accepted by the VyOS 1.5 validator.
Q3. An operator decommissions a tenant with `delete vrf name CUST-A` and commits. The commit is rejected. What is being enforced, and what is the correct commit?
CUST-A has eth1 enslaved to it and a default route configured under `vrf name CUST-A protocols static`. The operator deletes only the `vrf name CUST-A` subtree and commits. The commit is rejected and the running configuration is unchanged - eth1 is still in the VRF and traffic is still flowing.
Q4. `show configuration commands` shows eth1 bound to CUST-A, but the tenant subnet is reachable from the box's global context and `show ip route vrf CUST-A` is empty. What has happened and how is it repaired?
A VRF was built correctly weeks ago. During an unrelated incident somebody ran `ip link set dev eth1 nomaster` from the shell to test something and did not put it back. The configuration tree still says `vrf CUST-A`. A subsequent `commit` of unrelated changes did not restore the enslavement, because the interface node saw no change to apply.
Passing score: 75%. Answers are checked in this browser.