Skip to main content
RunBook Academy

VyOSXV · VRFsVRF

VRF configuration — set vrf name, table ids, attaching interfaces, addresses

Advanced⏱ ~22 minip link add VRFNAME type vrf table TABLEIDip link set dev SLAVEIF master VRFNAMEip -d link showip route show table TABLEIDip vrf showshow vrfshow interfaces ethernet SLAVEIFset vrf name VRFNAME table TABLEIDset interfaces ethernet SLAVEIF vrf VRFNAMEvyos

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

Not yet marked complete on this device.

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 1001 is dedicated to VRF CUST-A.
  • eth1 is enslaved to a Linux interface named CUST-A.
  • 10.1.0.0/24 and 2001:db8:1::/64 are connected routes in table 1001, not in table 254.
  • 10.1.0.254 is 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.

Read-only / SafeVyOS operational mode
$ show vrf
Name    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  eth2

Illustrative 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:

  1. show vrf lists the VRF with state up.
  2. ip -d link show dev CUST-A reports the device and vrf table 1001.
  3. ip -d link show dev eth1 reports master CUST-A and vrf_slave table 1001.
  4. ip route show table 1001 holds the connected routes, and ip route show table 254 does 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 in ip address show dev eth1 exactly as it would be on any interface.
  • A connected route 10.1.0.0/24 dev eth1 in 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 nomaster run by hand, or an interface recreated by something outside the configuration. The tell is ip -d link show dev eth1 with no master field while show configuration commands | match eth1 shows the binding. A commit of 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 100 mixes 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:

  1. compare to confirm the candidate diff reverses the forward change and removes every reference, not just the VRF.
  2. commit — a rejection here means a reference was missed, and the message names which kind.
  3. ip -d link show to confirm the VRF interface is gone and no port still reports it as a master.
  4. ip -4 rule show to confirm the rule chain matches the box’s new VRF count.
  5. save only after the kernel and the configuration agree.

Production discipline

Cross-course references

  • The Linux course’s V-Linux-NetConfig and XXI-Linux-NetAdvanced cover ip link add ... type vrf table N, ip link set ... master, and ip vrf exec from 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-concept explains the l3mdev model and the routing-policy rewrite that this lesson’s commit output performs.
  • vyos-xv-03-vrf-routing-protocols puts OSPF and BGP inside the VRF built here, and covers moving routes across the boundary deliberately.

Quiz

Knowledge check · 4 questions

  1. 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?

  2. Q2. `set vrf name MGMT table 100` is accepted by the VyOS 1.5 validator.

  3. 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.

  4. 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.