Skip to main content
RunBook Academy

← All break/fix scenarios in VyOS

advancedvyos-config-error~30 min

Remote Commit Locks Administrator Out

Reported symptoms

  • On Thursday morning nobody can SSH to `edge3`, `edge4` or `edge5` from the EU-South jump host. The sessions hang and eventually time out; they are not refused
  • The three routers answer ICMP, their BGP sessions are Established, and traffic through the region is completely unaffected
  • Monitoring is entirely green for all three, including the management-plane checks
  • The other eleven routers in the fleet, which received the identical change in the same window, are reachable normally
  • It is three routers in one region, which reads as a regional connectivity problem, and an hour goes to the transit provider before anyone suspects the routers
  • The engineer who made the change on Tuesday is certain they verified it. Their session log shows eleven unbroken minutes of connectivity after the commit, and they ran `confirm` and `save` at the end of it
  • The out-of-band runbook names a console server for the site. The hostname no longer resolves
  • A remote power cycle is requested and performed. It does not help

Evidence

  • · `ssh -v` from the EU-South jump host to `edge3` — stops after the connection attempt and times out, with no TCP reset
  • · `ping` from the same jump host to the same address — replies normally
  • · SSH from the central monitoring collector to `edge3` — connects immediately
  • · `show firewall ipv4 input filter` on `edge3` — the established-state rule is counting heavily, the SSH rule is counting a trickle, and the `default-action drop` counter is rising steadily
  • · `show configuration commands | match 'input filter rule 20'` — the SSH accept rule carries `source address 198.51.100.0/24`
  • · The EU-South jump host's actual address is in 203.0.113.64/26, and has been since a colo move in March
  • · The network register still lists the EU-South jump host in 198.51.100.0/24, which is where the change's source list came from
  • · `show system commit | head -4` on `edge3` — the Tuesday change, committed and confirmed, and the configuration is saved
  • · The nightly configuration backup for all three routers failed on Tuesday night and Wednesday night; the collector for that job runs on the regional jump host
Diagnosis and resolutionclick to reveal

Root cause

An audit finding required management services to accept connections only from known sources, and the change that closed it was built correctly: an explicit source list on the SSH and API accept rules in the input filter, rolled to fourteen routers in one window under `commit-confirm`. The source list was taken from the network register, which is the estate's system of record and which is wrong. The EU-South jump host moved to a new colo in March and was renumbered out of 198.51.100.0/24 into 203.0.113.64/26; DNS was updated in the same change and the register was not, so for five months every document said one thing and the network did another. Eleven routers are managed from jump hosts that never moved, which is why they were fine, and the three that were not are all in one region, which is why it presented as a regional network fault. The second half is why nobody caught it on Tuesday. The engineer tested from the jump host, over the SSH session they already had, and that session was never at risk: the input filter accepts established connections in an early rule, and a stateful firewall does not re-evaluate a flow that already has state. The change is invisible to every connection that exists when it is committed and applies only to connections that do not exist yet. So `commit-confirm` ran its full window with a live session as evidence, `confirm` was correct on that evidence, and `save` made it durable — which is why the power cycle later achieved nothing except a reboot into the same configuration. Three sound safety practices were followed exactly and none of them could see the fault, because all three were watching a connection that the change did not affect.

Remediation

Get in from a source the rule still permits before reaching for anything heavier. The routers are running, forwarding, and accepting management connections — from 198.51.100.0/24, which is where the central monitoring collector lives. That host is on the allowlist and it is the way back in. Console access would also have worked and does not exist here; a power cycle cannot work at all, because the configuration was saved and the router boots straight back into it. From the permitted host, add the current jump-host range to the SSH and API accept rules under `commit-confirm`, and then verify from a new connection opened from the jump host itself — not from the session you are already holding, which is the exact mistake that created the incident. Only after that new connection succeeds should you `confirm`, and only then `save`. Understand what widening the rule costs before you widen it: this control was added to close an audit finding, so adding a range to it is a change to a security control and belongs to the security owner as much as to the network team, and adding the range without correcting the register leaves the identical landmine armed for the next jump host that moves. Fix the register in the same ticket, and check the eleven routers that are working for the same latent dependency rather than treating them as unaffected.

Verification

A new SSH connection from the intended source, opened after the change, is the only test that means anything here. Not the session you are holding, not a reconnect from the host you fixed it from — a fresh connection from the jump host that could not get in, and the same for the HTTPS API if the API is part of the allowlist. Then read the counters rather than the intent: `show firewall ipv4 input filter` should show the management accept rule incrementing when you connect and the `default-action drop` counter no longer climbing in step with management attempts. Prove the control is still a control: connect from an address that is not on the list and confirm it is refused, because a rule that now permits everything would also make your test pass. Confirm the same on all three routers individually rather than inferring the other two from the first, since they were changed as a batch and can be wrong as a batch. Verify persistence honestly — the running configuration and the saved configuration must agree, so that a reboot does not restore the lockout. And close the loop off the routers: the nightly configuration backup for all three must succeed, which is the check that would have caught this on Tuesday night if anybody had been reading it.

Prevention

Never verify a management-path change on the connection you are already holding. A stateful firewall accepts established flows in an early rule, so your session is the one connection in the world that cannot detect the change; the test is a new connection, opened from the client that is supposed to be able to connect, by somebody who is not you. Size the `commit-confirm` window around that test rather than around how long the commit takes, and make opening the second session an explicit step in the procedure so it cannot be skipped under time pressure. Keep `save` out of the confirm window: the sequence is commit-confirm, test from a new session, confirm, then save, and a `save` typed early turns a self-reverting change into a permanent one and removes the reboot as a recovery path. Treat out-of-band access as a control that has to be exercised, not a hostname in a runbook — a console server that was decommissioned in May is a change to the recovery plan for every router behind it, and this estate discovered it had none at the moment it needed one. Build source allowlists from an inventory that is validated against reality rather than from a register that is updated by convention; a renumbering that updates DNS and not the register will produce this again, on a different region, with the same review passing. And read the failure signals you already collect: the configuration backup failed on Tuesday night, which was thirty-six hours before anybody noticed, and it failed for exactly the right reason.

Reported symptoms

On Tuesday an engineer closed an audit finding across the whole border fleet. The finding was that management services accepted connections from any source; the change added an explicit source list to the SSH and HTTPS API accept rules in each router’s input filter. Fourteen routers, one window, commit-confirm on every one, all fourteen confirmed and saved.

On Thursday morning the on-call engineer needs to look at edge3 during an unrelated partner circuit problem and cannot get in.

The picture that greets the bridge call is contradictory in every direction:

  • SSH to edge3, edge4 and edge5 from the EU-South jump host hangs and eventually times out. It is not refused — there is no immediate error, just silence.
  • All three routers answer ICMP from the same jump host. Their BGP sessions are Established. Traffic through the region is completely unaffected, and the partner circuit problem is real and now unfixable.
  • Monitoring is green for all three, including the management-plane checks.
  • The other eleven routers, which got the identical change in the same window, are reachable normally.

Three routers, one region, everything else healthy. The first hour goes to the transit provider, because that is what a regional problem looks like.

Two more facts arrive and make it worse rather than better. The engineer who made the change is certain they verified it, and their session log backs them up: eleven unbroken minutes of connectivity after the commit, then confirm, then save. And the out-of-band runbook names a console server for the site whose hostname no longer resolves.

Somebody requests a remote power cycle. It is performed. Nothing changes.

Evidence provided

Read-only / Safetimed out, not refused — nothing sent a reset
ops@jump-eu-south:~$ ssh -v edge3.eu-south.example.com
OpenSSH_9.2p1 Debian-2, OpenSSL 3.0.11
debug1: Connecting to edge3.eu-south.example.com [203.0.113.9] port 22.
ssh: connect to host edge3.eu-south.example.com port 22: Connection timed out

Illustrative output

Read-only / Safethe router is up and the path to it works
ops@jump-eu-south:~$ ping -c 3 edge3.eu-south.example.com
PING edge3.eu-south.example.com (203.0.113.9) 56(84) bytes of data.
64 bytes from 203.0.113.9: icmp_seq=1 ttl=61 time=8.42 ms
64 bytes from 203.0.113.9: icmp_seq=2 ttl=61 time=8.39 ms
64 bytes from 203.0.113.9: icmp_seq=3 ttl=61 time=8.44 ms

--- edge3.eu-south.example.com ping statistics ---
3 packets transmitted, 3 received, 0% packet loss

Illustrative output

Read-only / Safethe same router, the same moment, from a different source address
ops@collector01:~$ ssh vyos@edge3.eu-south.example.com 'show version | head -2'
Version:          VyOS 1.5-rolling
Release train:    circinus

Illustrative output

Read-only / Saferule 20 is counting, and so is the default drop
vyos@edge3:~$ show firewall ipv4 input filter
Rule     Action   Protocol  Source              Packets    Bytes
10       accept   all       0.0.0.0/0           4812390    1204M     state established
11       accept   all       0.0.0.0/0             21744     3021K     state related
15       drop     all       0.0.0.0/0                 12      768     state invalid
20       accept   tcp       198.51.100.0/24        1904     221K     dport 22
25       accept   tcp       198.51.100.0/24         840      96K     dport 443
30       accept   icmp      0.0.0.0/0             112044      9M
default  drop                                      36219     2174K

Illustrative output

Read-only / Safethe change, exactly as designed and reviewed
vyos@edge3:~$ show configuration commands | match 'input filter rule 2'
set firewall ipv4 input filter rule 20 action 'accept'
set firewall ipv4 input filter rule 20 protocol 'tcp'
set firewall ipv4 input filter rule 20 destination port '22'
set firewall ipv4 input filter rule 20 source address '198.51.100.0/24'
set firewall ipv4 input filter rule 25 action 'accept'
set firewall ipv4 input filter rule 25 protocol 'tcp'
set firewall ipv4 input filter rule 25 destination port '443'
set firewall ipv4 input filter rule 25 source address '198.51.100.0/24'

Illustrative output

Read-only / Safethe client the region is managed from; the register says 198.51.100.14
ops@collector01:~$ getent hosts jump-eu-south.example.com
203.0.113.71    jump-eu-south.example.com

Illustrative output

Read-only / Safeone change, committed and confirmed; the saved configuration matches it
vyos@edge3:~$ show system commit | head -4
0   2026-08-18 14:07:52 by netops via cli   SEC-1180 restrict mgmt sources
1   2026-06-02 10:44:19 by netops via cli   bgp timers per SEC-0994
2   2026-05-19 08:16:03 by netops via cli   syslog target change
3   2026-04-28 16:30:41 by netops via cli   interface description tidy

Illustrative output

Work the evidence before reading on

The router is refusing you and accepting somebody else at the same instant.

  1. SSH from the jump host times out; SSH from the collector succeeds; ping works from both. What is different between those two clients, and which field in rule 20 is it?
  2. Rule 20’s packet counter is not zero. Something is matching it. Whose traffic is that, and does a non-zero counter mean the rule is doing what it was written to do?
  3. Rule 10 accepts established connections and sits above rule 20. The engineer held a session through the commit and for eleven minutes afterwards. Which rule was carrying that session, and what would the change have had to do to interrupt it?
  4. commit-confirm, confirm and save were all used, in that order, by the book. Trace what each of them could have detected on Tuesday afternoon, given the answer to question 3.
  5. The power cycle did not help. What does save do, and what would a reboot have done if save had not been typed?

Then the question that resolves it: what test could the engineer have run on Tuesday that would have failed?

Root cause

1. The source list came from a register that has been wrong since March

The change is well built. An explicit source list on the management accept rules is exactly what the audit asked for, and it was rolled to the whole fleet in one window with a confirm timer on every router.

The source list came from the network register, which is the estate’s system of record for management ranges, and which says the EU-South jump host is in 198.51.100.0/24. It moved to a new colo in March and was renumbered into 203.0.113.64/26. DNS was updated in the same change. The register was not.

Eleven routers are managed from jump hosts that never moved, so eleven routers were unaffected. The three that were affected are all in one region because the stale entry is regional, which is precisely why the fault impersonated a regional network problem for the first hour.

The rule is doing exactly what it was told. Rule 20’s counter is non-zero because 198.51.100.0/24 is still a real range with real hosts in it — the central monitoring collector is one of them — so the rule looks alive, and its liveness is unrelated to whether it lets the right people in.

2. The change is invisible to every connection that already exists

This is the half that turns a routine mistake into a two-day outage.

The input filter accepts established connections in rule 10, above the management rules. That is the standard build and it is correct: a stateful firewall classifies each packet against the connection table first, and a packet belonging to a flow that already has state is accepted there and never reaches the rules further down.

The engineer’s SSH session was established before the commit. After the commit it kept matching rule 10, exactly as it had before, and nothing about it changed. It would have survived any source-list value at all, including one with no overlap with reality whatsoever.

So the change applies only to connections that do not exist yet, and the only connection available to test it with was one that could not be affected by it. The eleven minutes of unbroken connectivity in the session log are real, and they are evidence of nothing.

3. Three safety practices ran correctly and none of them could see it

commit-confirm schedules a rollback if the operator does not confirm. It protects against a change whose damage is visible inside the window. Here the window elapsed with a healthy session as the only signal.

confirm was the correct action given that signal. save persisted a configuration that had been tested as far as anybody knew how to test it.

And save is why the power cycle failed. Without it, a reboot would have brought the router up on the last saved configuration — the one from June — and restored access, which is why “reboot it” is a genuine recovery path on VyOS and why it is a genuine recovery path exactly once, before somebody saves.

4. The recovery path that was supposed to catch this had been removed

The out-of-band runbook names a console server for the site. It was decommissioned in May in a cost-saving exercise, and the runbook was not updated, so the estate believed it had console access to these routers and did not.

Nobody discovered that on a quiet afternoon. They discovered it on a bridge call, forty minutes in, which is the worst possible moment to learn that a control does not exist.

Resolution

  1. Get in from a source the rule still permits, before reaching for anything heavier. The routers are running and accepting management connections from 198.51.100.0/24, which is where the central monitoring collector lives. That host is the way in, and it is available immediately.
  2. Stop the power-cycle request. A reboot cannot help: the configuration was saved, so the router boots straight back into the rule that is refusing you, and the reboot costs a forwarding outage the region does not currently have.
  3. Confirm the diagnosis from the router before changing it. show firewall ipv4 input filter and the jump host's actual address take under a minute together, and they distinguish this from an SSH daemon problem, a routing problem or a transit problem — all of which are still live theories on the bridge.
  4. Widen the rule with the security owner, not around them. This control exists to close an audit finding, so adding a range to it is a change to a security control. That conversation is short, and skipping it is how a lockout incident becomes a compliance incident.
  5. Apply the correction under commit-confirm from the permitted host: add 203.0.113.64/26 to rules 20 and 25. One router first, not all three, so that a mistake costs one router rather than the region.
  6. Verify from a NEW connection from the jump host before confirming. Not the session you are holding, not a reconnect from the collector — a fresh connection from the client that could not get in. This is the step whose absence caused the incident, so it is the step that closes it.
  7. confirm, then save, in that order and not before the new connection succeeded. Then repeat on the remaining two routers and verify each one individually; they were changed as a batch and can be wrong as a batch.
  8. Correct the network register in the same ticket. Adding the range without fixing the source of truth leaves the identical trap armed for the next jump host that moves, and the change that moved this one updated DNS correctly, so the register is the part of the process that failed.
  9. Raise the console server separately and treat it as a finding in its own right. The estate believed it had out-of-band access to these routers for three months and did not, which is a gap in the recovery plan for every device behind it, not a detail of this incident.
  10. Check the eleven working routers for the same latent dependency rather than filing them as unaffected. They are working because their jump hosts have not moved yet.

Verification

  1. A new SSH connection from the EU-South jump host, opened after the change, succeeds. Not the session you are holding and not a reconnect from the host you fixed it from — the client that could not get in.
  2. The HTTPS API answers from the same client if the API is part of the allowlist. Two services were restricted and only one of them is the one everybody tested.
  3. The counters agree with the intent. show firewall ipv4 input filter shows the management accept rules incrementing as you connect, and the default-action drop counter no longer climbing in step with management attempts.
  4. The control is still a control. Connect from an address that is not on the list and confirm it is refused. A rule widened to 0.0.0.0/0 by accident would also make every check above pass.
  5. All three routers pass individually. They were changed as a batch, so infer nothing about the second and third from the first.
  6. The running and saved configurations agree, so a reboot restores access rather than removing it. This is the property that made the power cycle useless, and it is worth confirming deliberately rather than assuming.
  7. The nightly configuration backup succeeds for all three routers. That job failed on Tuesday night and again on Wednesday night, thirty-six hours before anybody noticed, and it is the off-router signal that this class of fault produces first.

Prevention

  • Never verify a management-path change on the connection you are already holding. The established-state rule accepts your session regardless of what you just changed, so the test is a new connection from the intended client, ideally opened by somebody who is not you.
  • Make the second session an explicit step in the remote-change procedure, and size the commit-confirm window around how long that takes rather than around how long the commit takes. A step that is implicit is a step that is skipped under pressure.
  • Keep save out of the confirm window. The sequence is commit-confirm, test from a new session, confirm, save. A save typed early turns a self-reverting change into a permanent one and removes the reboot as a recovery path, which is two safety nets lost to one keystroke of muscle memory.
  • Treat out-of-band access as a control to exercise, not a hostname in a runbook. Connect to it on a schedule. A console server decommissioned in May is a change to the recovery plan for every router behind it.
  • Build source allowlists from an inventory validated against reality, not from a register updated by convention. The renumbering here updated DNS and not the register, and the review that approved the change checked it against the register, so every step of the process was internally consistent and wrong.
  • Read the signals you already collect. The configuration backup failed the same night, for exactly the right reason, into a digest nobody opens.