Skip to main content
RunBook Academy

VyOSV · Configuration ModelConfigure mode

Common configuration traps — patterns that bite every operator at least once

Intermediate⏱ ~16 minconfigurecommitsavecompareshow configuration commands | match

What you'll learn

  • Recognise the configuration traps that lock out operators or break routing
  • Apply the safe-change pattern that prevents each trap
  • Read the running configuration to confirm a trap has not been tripped
  • Recover from each trap with the documented playbook

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.

Common configuration traps — patterns that bite every operator at least once

A handful of configuration patterns trip up every VyOS operator at least once. The patterns are not subtle; they are the obvious mistakes that the documentation does not always highlight. This lesson is the catalog of traps and the recovery playbook for each.

Every trap below is split the same way, because the split is the skill: what the CLI catches for you versus what the CLI accepts happily and lets you discover in production. The first group is annoying. The second group is the one that pages you.

Trap 1 — commit without save

[edit]
vyos@vyos# set system host-name 'router-core-02'
[edit]
vyos@vyos# commit
[edit]
vyos@vyos# exit

The change is live. The next reboot reverts it. The operator forgets, the box reboots, the hostname is back to router-core-01, and the monitoring system reports a state mismatch.

commit moves the candidate into the running configuration. save writes the running configuration to /config/config.boot, which is the file the router reads at boot. They are two different operations against two different pieces of state, and nothing in VyOS couples them for you.

Detection: from configure mode, compare saved diffs the candidate against the saved configuration. On a router where everything has been saved it prints nothing. On a router carrying committed-but-unsaved changes it prints exactly the set of lines that will disappear at the next reboot — which makes it the single most useful command in this lesson.

Recovery: if the box has not rebooted yet, save is the whole fix. If it has, the change is gone from the running configuration but the commit itself is still in the revision archive: show system commit lists the archived revisions with their timestamp and the user who made them, and compare against a revision number shows what that revision contained, so you can retype the line you lost and this time save it.

Trap 2 — wrong scope in a nested edit

[edit]
vyos@vyos# edit protocols bgp
[edit protocols bgp]
vyos@vyos# set interfaces ethernet eth0 description 'BGP-PEER'

The operator meant to describe an interface but typed the absolute path from inside the BGP scope, so the CLI resolved it as protocols bgp interfaces ethernet eth0 description. That path does not exist in the schema, so the set is rejected on the spot — before any commit — with a complaint that the path is not valid.

This is the benign form of scope drift, and it is worth understanding precisely why it is benign: the path you typed happened to be invalid in the scope you were in. The CLI is not checking your intent; it is checking the schema. When a path is valid in both scopes, nothing objects at all — see trap 10.

Recovery: top returns to the root of the tree, up moves one level out. The prompt always tells you where you are — the [edit protocols bgp] line is not decoration.

Trap 3 — an unquoted value that contains a space

[edit]
vyos@vyos# set interfaces ethernet eth0 description Uplink to ISP

VyOS splits the command on whitespace and walks the schema token by token. Uplink, to and ISP are read as three more path components under description, which takes a single value, so the command is rejected. The operator sees an error that names the path rather than the quoting, retypes something slightly different, and loses several minutes.

The version that actually hurts is the one that succeeds: a value whose first word is meaningful on its own. set system name-server or a syslog facility string will happily accept the first token and drop the rest of your intent on the floor.

Recovery: quote every value that contains a space, a #, a ; or a !.

[edit]
vyos@vyos# set interfaces ethernet eth0 description 'Uplink to ISP'

show configuration commands re-emits every value in single quotes for exactly this reason — the output is designed to be pasted back in, and unquoted multi-word values would not survive the round trip.

Trap 4 — DHCP on the wrong interface

[edit]
vyos@vyos# set interfaces ethernet eth0 address 'dhcp'
[edit]
vyos@vyos# commit
[edit]
vyos@vyos# save

The operator intended to set eth1 to DHCP (the WAN-facing interface) but typed eth0 (the LAN-facing interface). Note what did not happen: nothing was removed. address is a multi-value node, so eth0 now holds both its static address and a DHCP client, and the box brings up a second address from whatever answers on the LAN.

If the operator was connected over eth0, the outcome is worse than a routing mistake — the default route learned from the LAN DHCP server can displace the real one, and the SSH session dies mid-commit.

Recovery:

[edit]
vyos@vyos# delete interfaces ethernet eth0 address 'dhcp'
[edit]
vyos@vyos# set interfaces ethernet eth1 address 'dhcp'
[edit]
vyos@vyos# compare
[edit]
vyos@vyos# commit
[edit]
vyos@vyos# save

Deleting with the value (address 'dhcp') removes just that entry. delete interfaces ethernet eth0 address with no value removes every address on the interface, including the static one you are connected to. The difference between those two commands is a truck roll.

Trap 5 — a firewall group that matches nothing

The 1.4+ firewall tree keeps groups in one place and rules in another:

[edit]
vyos@vyos# set firewall group network-group NETWORKS network '10.0.0.0/8'
[edit]
vyos@vyos# set firewall ipv4 name WAN-IN rule 10 action 'drop'
[edit]
vyos@vyos# set firewall ipv4 name WAN-IN rule 10 source group network-group 'NETWORKS'
[edit]
vyos@vyos# commit

Two distinct failures hide in that pair, and they behave very differently:

  • A group name that does not existNETWROKS instead of NETWORKS — is caught at commit. VyOS resolves group references during verification and refuses the commit, naming the invalid group and the rule it appears on. This one the CLI catches for you.
  • A group that exists but has no members is not an error. It commits, with a warning that the group is empty, and the rule is installed against an empty set. A match against an empty set never succeeds, so the rule never fires and traffic falls through to whatever the ruleset’s default-action is. If that default is accept, you now have a firewall that reports “configured” and filters nothing.

The second case is the dangerous one because it is produced by ordinary operations: someone deletes the last member of a group during cleanup, or an automation run creates the group before the run that populates it.

Recovery: show firewall group lists the groups and their members; a group with an empty member list is the finding. show firewall shows the compiled rules with their counters — a rule that should be matching and shows zero packets after a representative period of traffic is telling you the same thing from the other direction.

Trap 6 — a ruleset nothing jumps to

This trap is new in 1.4 and catches everyone migrating from 1.3.

On 1.3, a named ruleset was attached to an interface and a direction: set interfaces ethernet eth0 firewall in name WAN-IN. That binding no longer exists. From 1.4 onward, traffic reaches a named ruleset only because a rule in one of the base hooks sends it there:

[edit]
vyos@vyos# set firewall ipv4 forward filter rule 10 inbound-interface name 'eth0'
[edit]
vyos@vyos# set firewall ipv4 forward filter rule 10 action 'jump'
[edit]
vyos@vyos# set firewall ipv4 forward filter rule 10 jump-target 'WAN-IN'

A firewall ipv4 name WAN-IN block with no jump pointing at it is valid configuration. It commits without complaint, it appears in show configuration, it looks exactly like a working firewall in a config review — and no packet ever reaches it. Traffic transiting the router is evaluated by forward filter, traffic addressed to the router itself by input filter, and traffic the router originates by output filter.

Recovery: for every named ruleset, confirm something jumps to it.

show configuration commands | match 'jump-target'

Then confirm the jump is being taken, by reading the counters on the jumping rule rather than on the ruleset:

show firewall ipv4 forward filter

Trap 7 — MTU mismatch on a tunnel

[edit]
vyos@vyos# set interfaces wireguard wg0 mtu '1420'
[edit]
vyos@vyos# commit
[edit]
vyos@vyos# save

1420 is the correct WireGuard MTU when the underlay is a plain 1500-byte Ethernet path: WireGuard adds 60 bytes of overhead over IPv4 (80 over IPv6). It is the wrong number the moment the underlay is smaller — a PPPoE WAN gives you 1492, so the tunnel needs 1412, and any additional encapsulation upstream shrinks it further.

The symptom is not “the tunnel is down”. The tunnel comes up, ping works, SSH works until you cat a large file, and HTTPS to some sites hangs after the handshake. That signature — small packets fine, large flows stalled — is path MTU discovery being black-holed: the oversized packet is dropped somewhere that does not return ICMP “fragmentation needed”, so the sender never learns to send less and simply retransmits forever.

Recovery: two levers, and they solve different halves.

[edit]
vyos@vyos# set interfaces wireguard wg0 mtu '1412'
[edit]
vyos@vyos# set interfaces wireguard wg0 ip adjust-mss '1372'

The mtu value fixes what the router itself will emit. The ip adjust-mss value rewrites the MSS option in TCP handshakes crossing the interface, so the two endpoints negotiate a segment size that fits without depending on ICMP surviving the path. Use ipv6 adjust-mss for the v6 side; on 1.3 both of these lived under a single tcp-mss node.

Prove the path rather than assuming it, from op mode:

Read-only / Safeop mode
vyos@vyos:~$ ping 10.10.0.2 size 1372 do-not-fragment count 5

Raise the size until it fails; the last size that succeeds is your real payload budget. do-not-fragment is the important half — a probe that is allowed to fragment tells you nothing about the path MTU, because it will succeed at every size.

Trap 8 — two default routes and an asymmetric return path

[edit]
vyos@vyos# set protocols static route 0.0.0.0/0 next-hop '192.0.2.1'
[edit]
vyos@vyos# commit
[edit]
vyos@vyos# save

The operator added a second default without removing the first. next-hop is a multi-value node, so nothing was replaced. Two next-hops at the same administrative distance become one multipath route, and the kernel spreads flows across them by hash. Traffic leaves by one ISP and its replies come back via the other, which stateful firewalls upstream will drop as an out-of-state flow.

Detection: show ip route 0.0.0.0/0 shows both next-hops under one entry. A multipath default is a deliberate design or a mistake — the routing table cannot tell you which, so you have to.

Recovery: delete the unintended next-hop, or make the preference explicit if both are wanted as primary and backup:

[edit]
vyos@vyos# set protocols static route 0.0.0.0/0 next-hop '192.0.2.1' distance '10'
[edit]
vyos@vyos# set protocols static route 0.0.0.0/0 next-hop '198.51.100.1' distance '20'

Now only the distance-10 next-hop is installed, and the distance-20 one takes over when the first is withdrawn.

Trap 9 — SSH key pasted as one blob

The operator copies a line out of authorized_keys and pastes the whole thing into the key leaf:

set system login user vyos authentication public-keys operator@laptop key 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA...'

VyOS stores the algorithm and the key material in two separate leaves. The key leaf takes the base64 body only; the algorithm goes in type:

[edit]
vyos@vyos# set system login user vyos authentication public-keys operator@laptop key 'AAAAC3NzaC1lZDI1NTE5AAAA...'
[edit]
vyos@vyos# set system login user vyos authentication public-keys operator@laptop type 'ssh-ed25519'

Paste the algorithm prefix into key and the generated authorized_keys entry is malformed, so the key never matches. Add a stray character from the clipboard and you get the same outcome for a different reason. Either way, SSH answers with Permission denied (publickey) and the operator who has already disabled password login is locked out.

Recovery: VyOS ships a loadkey helper in op mode that reads a standard authorized_keys file and splits it into the type and key pair for you — using it removes the whole class of mistake. If the box is already unreachable, recovery is console or hypervisor/IPMI access: delete the bad key, add the correct pair, commit, save.

Trap 10 — VRF scope drift

This is trap 2 with the safety catch removed.

[edit]
vyos@vyos# edit vrf name mgmt
[edit vrf name mgmt]
vyos@vyos# set protocols ospf area 0 network '10.0.0.0/24'
[edit vrf name mgmt]
vyos@vyos# commit

The operator wanted OSPF in the global table. The path protocols ospf ... is valid inside a VRF as well as at the top of the tree, because VyOS 1.4+ runs a routing-protocol instance per VRF. So there is no error at set, no error at commit, and a show configuration that reads correctly if you skim past the indentation. OSPF is running — in a table that carries no production traffic. No adjacency forms with the neighbours you expected, and the global table has no OSPF routes at all.

Detection: the prompt told you ([edit vrf name mgmt]), and so does the operational view — show ip ospf neighbor looks at the default table, while the VRF instance needs show ip ospf vrf mgmt neighbor. A protocol that appears configured but has no neighbours in the default table, and neighbours you did not intend in a VRF, is this trap.

Recovery:

[edit vrf name mgmt]
vyos@vyos# top
[edit]
vyos@vyos# delete vrf name mgmt protocols ospf
[edit]
vyos@vyos# set protocols ospf area 0 network '10.0.0.0/24'
[edit]
vyos@vyos# compare
[edit]
vyos@vyos# commit

The habit that prevents it: run top before starting an unrelated change, and read compare output for the leading path of every added line, not just the tail.

Trap 11 — commit-confirm not confirmed

The operator used commit-confirm 5 to guard a risky change, got pulled into a meeting, and did not run confirm. The router reverted.

This is the mechanism working exactly as designed, and the trap is not the revert — it is what the revert costs. The safety net restores the previous configuration in full, not just the line you were testing, so any other change committed in that window goes with it. Check what commit-confirm does on your release before you schedule one during a maintenance window with several operators on the box: this is one of the places VyOS behaves differently from the Junos commit confirmed that people expect, and the revert is not guaranteed to be a quiet in-place edit.

The subtler trap: running save while a commit-confirm timer is pending. The revert restores the previously saved state, and you have just overwritten it with the change you are still testing. The timer expires, the “safe” configuration is restored, and it is the broken one. Confirm first, save second — in that order, always.

Recovery: re-apply the change, this time running confirm as soon as the change is verified live, and only then save.

How the result is validated

Three commands cover every trap above:

compare
compare saved
show configuration commands | match 'ethernet eth0'

compare shows candidate versus running — what commit is about to do. compare saved shows candidate versus the file on disk — what a reboot is about to undo. show configuration commands prints the whole configuration as a flat list of set lines, which is the form that greps cleanly and the form you paste into a change ticket.

How it fails

The common meta-trap that produces most of the above:

  • Operator does not compare before commit. A diff would have shown the wrong scope, the wrong interface, the typo. The operator commits and finds out at runtime.
  • Operator reads the configuration instead of the counters. Every trap in the second half of this lesson produces a configuration that reads correctly. show firewall, show ip route and show ip ospf neighbor are the ones that disagree with it.
  • Operator does not test in a lab first. A production box is the wrong place to discover that DHCP on the wrong interface breaks routing.
  • Operator does not have console access. The configuration change locks them out and there is no recovery path that does not require console.

Production discipline

Cross-course references

The Linux course’s V-Linux-NetConfig covers the underlying network configuration. The Ansible course’s XLII-Ansible-BeyondLinux covers how to avoid these traps by driving configure mode from automation with review gates.

Quiz

Knowledge check · 5 questions

  1. Q1. Which single command, if missed, causes a committed change to revert on reboot?

  2. Q2. A firewall rule whose source group exists but has no members matches no traffic, so the packets fall through to the ruleset's default-action.

  3. Q3. On VyOS 1.5 you have committed `set firewall ipv4 name WAN-IN rule 10 action drop` and nothing else about WAN-IN. What happens to transit traffic?

  4. Q4. An operator sets a default route to a wrong next-hop and commits. Traffic to the public Internet fails. What is the most likely cause and how is it detected?

    The operator intended to point the default route at the upstream ISP gateway but typed the wrong IP. The change is live and `show ip route` shows the bad next-hop.

  5. Q5. An operator adds an SSH key by pasting a whole authorized_keys line into the `key` leaf. The operator is now locked out of the box. What is the recovery?

    The operator pasted `ssh-ed25519 AAAA...` into `system login user vyos authentication public-keys operator@laptop key`, committed, saved, disabled password login, and tried to SSH. The connection is rejected. The operator has no console access.

Passing score: 75%. Answers are checked in this browser.