Skip to main content
RunBook Academy

VyOSIII · VyOS ArchitectureArchitecture

The configuration tree — where every set command lives

Intermediate⏱ ~14 minconfiguresetcommitshowvyos

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)

Not yet marked complete on this device.

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:

  • interfaces is applied to the kernel directly. The address lands on eth0 through netlink, the same state you would get from ip 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 route becomes a route in staticd, and zebra is what installs it into the kernel FIB. The same is true of protocols 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 configuration prints in operational mode, and what is written to /config/config.boot by save. It reads as nested blocks and is what you diff between routers.
  • set-command form — what show configuration commands prints: one flattened set line 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 path1.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 / locala rule under firewall ipv4 forward filter (or input / output) with action jump
interfaces ... firewall ... tcp-mssip adjust-mss (and ipv6 adjust-mss)
state { established enable }state established as a leaf on its own
nat source rule N outbound-interface eth0nat 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 configuration shows the active configuration.
  • ip route show shows the actual kernel state.
  • compare saved shows how the running configuration differs from the file on disk.

Fix:

  • rollback 1; commit to revert.
  • Or delete ...; commit to 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 configuration shows the active configuration.
  • cat /config/config.boot shows the saved configuration.
  • Compare them.

Fix:

  • Re-save after fixing the disk issue.
  • Or manually edit /config/config.boot to 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 log shows the automatic rollback.
  • show configuration shows 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”:

  1. The running configuration: show configuration shows what the router has been told to do.
  2. The candidate configuration: inside configure, show shows what would be applied on commit.
  3. The diff: compare shows candidate against running; compare saved shows candidate against the file on disk.
  4. The forwarding state: show ip route reads FRR’s RIB; from a shell, ip route show reads the kernel FIB and sudo nft list ruleset reads the firewall the firewall tree actually produced.
  5. The saved file: cat /config/config.boot shows 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-NetConfig covers 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

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

  2. Q2. Which command shows the diff between the candidate and active configurations?

  3. Q3. A `save` is needed to make a `commit-confirm` survive a reboot.

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