VyOSIII · VyOS ArchitectureArchitecture
The configuration tree — where every set command lives
What you'll learn
- Describe the VyOS configuration tree structure and the path conventions
- Explain how a `set` command translates into kernel / FRRouting state
- Use `compare`, `show configuration`, and `merge` to manage the configuration safely
- Recognise the configuration-tree failure modes
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)
The VyOS configuration tree is the canonical place where every
configuration parameter lives. The set command adds a leaf;
the commit command applies the tree to the kernel; the
save command persists the active configuration to the boot
image. The operator who understands the tree can read any
configuration file and know what every line does.
This lesson is the operator’s foundation in the configuration tree: the path conventions, the commit / save semantics, and the operational commands that make the tree safe to use.
Configuration tree structure
The VyOS configuration tree is a hierarchical namespace. Every
configuration parameter has a path of space-separated nodes
from the root to a leaf value — which is why a set command reads
as a sentence rather than a filesystem path.
# Interface configuration
set interfaces ethernet eth0 address 192.0.2.50/24
set interfaces ethernet eth0 description "WAN uplink"
set interfaces ethernet eth0 mtu 1500
# BGP configuration
set protocols bgp system-as 65000
set protocols bgp parameters router-id 1.1.1.1
set protocols bgp neighbor 192.0.2.1 remote-as 65001
set protocols bgp neighbor 192.0.2.1 address-family ipv4-unicast
# Firewall configuration
set firewall ipv4 name WAN-INBOUND default-action drop
set firewall ipv4 name WAN-INBOUND rule 10 action accept
set firewall ipv4 name WAN-INBOUND rule 10 protocol tcp
set firewall ipv4 name WAN-INBOUND rule 10 destination port 22
# A named ruleset is inert until a base hook jumps into it
set firewall ipv4 input filter rule 10 inbound-interface name eth0
set firewall ipv4 input filter rule 10 action jump
set firewall ipv4 input filter rule 10 jump-target WAN-INBOUND
Read the BGP branch carefully, because its shape is the thing
operators get wrong most often on a current release. The ASN is a
leaf (system-as 65000), not a node you descend through, and
neighbor is a sibling of it rather than a child. A configuration
written as set protocols bgp 65000 neighbor ... — the shape VyOS
used up to 1.3 — is rejected by the parser on 1.4 and 1.5 because
there is no numeric node at that level to descend into.
The firewall branch carries the other trap. firewall ipv4 name WAN-INBOUND creates a ruleset; it does not attach one. Nothing
traverses those rules until a base chain — input filter,
forward filter or output filter — carries a rule whose action is
jump and whose jump-target names it. A committed ruleset with no
jump is a tree branch that exists, validates, and filters nothing.
The tree is rooted at the implicit root; each set command
walks the tree from the root to the leaf, creating nodes as
needed.
flowchart TB
R["config tree root"]
R --> I["interfaces"]
R --> P["protocols"]
R --> F["firewall"]
R --> SYS["system"]
I --> I1["ethernet"]
I1 --> I2["eth0"]
I2 --> I3["address: 192.0.2.50/24"]
I2 --> I4["description: WAN uplink"]
P --> P1["bgp"]
P1 --> P2["system-as: 65000"]
P1 --> P3["neighbor: 192.0.2.1"]
P3 --> P4["remote-as: 65001"]
F --> F0["ipv4"]
F0 --> F1["name: WAN-INBOUND"]
F1 --> F2["rule: 10"]
F2 --> F3["action: accept"]
F0 --> F4["input filter"]
F4 --> F5["rule: 10"]
F5 --> F6["jump-target: WAN-INBOUND"]
The tree’s structure is documented in the VyOS source code and the official documentation. The operator who knows the structure can navigate the tree without the docs.
commit, save, and the three configurations
VyOS maintains three configurations:
- Candidate — the configuration being edited. Lives only in memory until
commit. - Active — the configuration currently applied to the kernel. Updated by
commit. - Saved — the configuration that survives reboot. Updated by
save.
The lifecycle:
sequenceDiagram
participant O as Operator
participant V as VyOS CLI
participant M as Memory
participant K as Kernel
participant D as Disk
O->>V: configure
O->>V: set ...
V->>M: update candidate
O->>V: commit
V->>K: apply diff (nft, ip, vtysh)
M->>K: copy candidate to active
O->>V: save
V->>D: write active to /config/config.boot
O->>V: exit
commit alone is not enough — the configuration is in the
active set but not on disk. A reboot will revert to the saved
configuration. The standard pattern: commit; verify the
change is live; save; verify the file is updated.
Operational commands inside configure
configure
# Add configuration
set interfaces ethernet eth0 address 192.0.2.50/24
# Move the edit level down into a subtree, so subsequent set and
# delete commands are relative to it. `top` returns to the root,
# `up` climbs one level.
edit interfaces ethernet eth0
set description "WAN uplink"
top
# Show the candidate, with pending changes marked + and -
show
# Show only the pending changes: candidate against running
compare
# Show the pending changes against the config on disk
compare saved
# Reach an operational-mode command without leaving configure
run show interfaces
# Commit the candidate to the running configuration
commit
# Persist the running configuration to /config/config.boot
save
# Throw away the candidate and start again from the running config
discard
# Exit configure
exit
Two habits carry most of the safety here. The first is compare
before every commit: it shows the diff between the candidate and
the running configuration, and that diff is the review surface — a
change you did not see in the diff is a change you cannot defend.
The second is compare saved after every commit: if it prints
anything, the running configuration and the boot configuration have
diverged and the change you just made will not survive a reboot.
edit is worth its own note because its name misleads. It does not
open a text editor. It moves the CLI’s current position in the tree,
so edit interfaces ethernet eth0 followed by show shows only that
interface’s subtree, and a delete typed at that level deletes
relative to it. That relative delete is the sharpest edge in
configure mode — check where you are with show before you use it.
Translation from set to kernel state
When the operator runs commit, VyOS computes the diff between the
candidate and the running configuration and hands each changed
subtree to the script that owns it. Those scripts do not all target
the same place, and that is the single most useful fact in this
lesson for troubleshooting.
# Operator runs:
set interfaces ethernet eth0 address 192.0.2.50/24
set protocols static route 0.0.0.0/0 next-hop 192.0.2.1
set protocols bgp system-as 65000
commit
Three subtrees, two destinations:
interfacesis applied to the kernel directly. The address lands oneth0through netlink, the same state you would get fromip addr add 192.0.2.50/24 dev eth0.protocols— static routes included — is rendered into FRRouting’s configuration and reloaded into the daemons. VyOS has not owned static routing itself since it moved routing into FRR:set protocols static routebecomes a route instaticd, and zebra is what installs it into the kernel FIB. The same is true ofprotocols bgp, which is why the BGP tree tracks FRR’s shape and changes when FRR’s does.
That split is why a missing route needs two questions, not one.
show ip route 0.0.0.0/0 asks whether FRR has the route at all; the
kernel routing table, read from a shell with ip route show, asks
whether zebra managed to install it. A route present in the first and
absent from the second is a different fault — and a different fix —
from a route absent in both.
Two renderings of the same tree
The tree has one canonical form and two textual renderings, and knowing which one you are looking at saves an argument in a change review.
- Curly-brace form — what
show configurationprints in operational mode, and what is written to/config/config.bootbysave. It reads as nested blocks and is what you diff between routers. set-command form — whatshow configuration commandsprints: one flattenedsetline per leaf, the same lines you would type. This is the form to paste into a change ticket, because it is directly re-appliable.
Both describe the same tree. Neither is a separate “format” you can configure the router in; the tree is the configuration, and these are views of it.
Loading a configuration from a file is load:
configure
load /config/config.boot
compare
commit
save
exit
load replaces the candidate outright, so everything not in the
file is gone once you commit. merge is the other half of the pair:
it overlays the file onto the candidate, leaving anything the file
does not mention untouched. Choose deliberately — the two produce
identical output on a router whose configuration already matches the
file, and wildly different output on one that does not.
Either way, compare between the load and the commit is not
optional. It is the only step that shows you what the file is about
to remove.
Paths are versioned, and 1.4 moved several of them
A configuration path is an interface, and interfaces change between
releases. VyOS 1.4 (sagitta) restructured several trees and 1.5
(circinus) kept the new shape, so a config.boot taken off a 1.3
router does not simply load into a current one — the file parses,
and then commit rejects the nodes that no longer exist.
The moves worth memorising, because they cover most of a real configuration:
| 1.3 path | 1.4 / 1.5 path |
|---|---|
protocols bgp <asn> ... | protocols bgp system-as <asn>, peers under protocols bgp neighbor ... |
protocols bgp <asn> network <prefix> | protocols bgp address-family ipv4-unicast network <prefix> |
firewall name <n> | firewall ipv4 name <n> |
zone-policy zone <z> | firewall zone <z> |
per-interface firewall in / out / local | a rule under firewall ipv4 forward filter (or input / output) with action jump |
interfaces ... firewall ... tcp-mss | ip adjust-mss (and ipv6 adjust-mss) |
state { established enable } | state established as a leaf on its own |
nat source rule N outbound-interface eth0 | nat source rule N outbound-interface name eth0 |
The last two are the ones that fail quietly rather than loudly. A
state node written the 1.3 way is a validation error you will see
immediately; a source NAT rule that survives the migration without a
translation address masquerade clause commits cleanly and
translates nothing, which you will discover from the far end of the
link.
The upgrade path VyOS supports is an image upgrade, which runs migration scripts against the stored configuration on first boot. The failure mode above is what happens when someone bypasses that by hand-loading an old file into a fresh install.
Failure modes
Commit succeeds but configuration is wrong
The operator commits a configuration that does not match their intent. The commit succeeds; the routing engine accepts the configuration; the kernel receives the netlink messages.
Diagnostic:
show configurationshows the active configuration.ip route showshows the actual kernel state.compare savedshows how the running configuration differs from the file on disk.
Fix:
rollback 1; committo revert.- Or
delete ...; committo remove the offending lines.
Save fails partway
The save command writes the active configuration to disk. If
the write fails partway (disk full, NFS hung, etc.), the saved
configuration may be corrupt or partial.
Diagnostic:
show configurationshows the active configuration.cat /config/config.bootshows the saved configuration.- Compare them.
Fix:
- Re-save after fixing the disk issue.
- Or manually edit
/config/config.bootto ensure consistency.
Commit-confirm rolls back unexpectedly
The commit-confirm N command is the safety net for remote
changes. If the operator does not confirm within N minutes, the
configuration reverts. The operator who was disconnected and did
not reconnect in time sees the rollback.
Diagnostic:
show logshows the automatic rollback.show configurationshows the rolled-back state.
Fix:
- Reconnect, run the configuration again, and confirm within the timeout.
Operational commands
The operator reads the configuration with:
# Operational mode: the running configuration, curly-brace form
show configuration
# Operational mode: the same tree as re-appliable set lines
show configuration commands
# Scope it with the CLI pipe, which every show command accepts
show configuration commands | match "protocols bgp"
show configuration commands | strip-private
# Configuration mode: scope by moving the edit level instead
configure
show protocols bgp
show interfaces ethernet eth0
exit
There is no separate operational command that takes a subtree path.
Scoping is either a pipe on show configuration commands or an
edit level inside configure — and the second is the one to reach
for during a change, because it is the same position your set and
delete commands will act from.
The show configuration variants are the operator’s tools for
understanding what the router is configured to do. The
operator who has never read the running configuration does not
know what the router thinks it is doing.
Validation
The validation sequence for “the configuration is wrong”:
- The running configuration:
show configurationshows what the router has been told to do. - The candidate configuration: inside
configure,showshows what would be applied on commit. - The diff:
compareshows candidate against running;compare savedshows candidate against the file on disk. - The forwarding state:
show ip routereads FRR’s RIB; from a shell,ip route showreads the kernel FIB andsudo nft list rulesetreads the firewall thefirewalltree actually produced. - The saved file:
cat /config/config.bootshows what survives reboot.
Steps 1 and 4 answer different questions and are worth running
together. Step 1 is intent; step 4 is what the router is doing. When
they disagree — a firewall ipv4 name ruleset that no nft chain
jumps to, a static route in the configuration and not in the FIB —
the gap between them is the fault. If the saved file matches the
running configuration, the change survives reboot; if it does not,
compare saved will say so before the reboot does.
Cross-course references
- The Linux course’s
XX-Linux-NetConfigcovers the underlying kernel state the configuration tree translates to. - The OPNsense course covers the equivalent FreeBSD configuration model.
- The lesson on commit-confirm and remote change discipline in Part VI covers the safe-change mechanics in depth.
Quiz
Knowledge check · 4 questions
Q1. You commit a static route. The route appears in `ip route show`. After a router reboot, the route is gone. What is the most likely cause?
The operator runs: `set protocols static route 10.0.0.0/24 next-hop 192.0.2.1` `commit` After commit, `ip route show 10.0.0.0/24` shows the route. The operator does not run `save`. The router reboots. After reboot, the route is gone.
Q2. Which command shows the diff between the candidate and active configurations?
Q3. A `save` is needed to make a `commit-confirm` survive a reboot.
Q4. You commit-confirm with a 5-minute window. You get disconnected at minute 3. The route change is verified working. What happens at minute 5?
The operator runs `commit-confirm 5`. The change is applied. The operator validates the change is working at minute 3. The operator gets disconnected (network outage, laptop crash, etc.). At minute 5, what happens?
Passing score: 75%. Answers are checked in this browser.
Production discipline
The configuration tree is the canonical place where every
parameter lives. Plan the configuration once. Document the
discipline (commit; verify; save; verify). Use
commit-confirm for every remote change. Then either the
configuration persists correctly or the operator knows where
to find the failure.