Skip to main content
RunBook Academy

← All break/fix scenarios in VyOS

intermediatevyos-default-route~30 min

Wrong Default Route

Reported symptoms

  • Finance, not the NOC, raised the ticket: ISP-B has been billing above its 95th-percentile commit for two months while ISP-A, the contracted primary, bills at almost nothing
  • A partner API began rejecting the site two days ago with an IP-allowlist error, after the partner tightened their allowlist to the address the site documented
  • Inbound traffic still arrives over ISP-A, so the path is asymmetric, and a handful of long-lived sessions through a stateful middlebox drop intermittently
  • `show ip bgp summary` is clean: both sessions Established for weeks, prefix counts as expected, no flaps
  • An engineer already fixed this a week ago by raising the local-preference on the ISP-A default to 200. The change committed, it is visible in the BGP table, and nothing moved
  • Nobody can point at a change that explains it. The last routing change anyone remembers was six weeks ago and was backed out at the end of the window

Evidence

  • · `show ip route 0.0.0.0/0` — a static default via 198.51.100.1 is selected; the eBGP default via 203.0.113.1 is present as a candidate and is not
  • · `show ip bgp 0.0.0.0/0` — the ISP-A path carries localpref 200 and is marked best among the BGP paths
  • · `show configuration commands | match "static route 0.0.0.0/0"` — returns one line nobody expected to be there
  • · `show system commit` — a six-week-old revision with the comment "temp default during ISP-A window"
  • · `traceroute -n 198.18.0.1` from the router and from a host in the DC — both leave via 198.51.100.1
  • · `show interfaces ethernet eth1` against `eth0` — egress bytes are almost entirely on the ISP-B interface
  • · `show ip bgp neighbor 203.0.113.1 received-routes` — the default is being received normally and always has been
Diagnosis and resolutionclick to reveal

Root cause

Six weeks ago, during a window in which the ISP-A eBGP session was deliberately down, an engineer added a static default route toward ISP-B to keep the site online. It worked, the window closed, the session came back, and the static was never removed. A static route carries administrative distance 1 and an eBGP-learned route carries 20, so from the moment the session recovered the static won every selection and the site's entire egress moved to the backup provider — silently, because the backup provider works. There was a default, it was selected, traffic flowed, and no alarm exists for "the right traffic is leaving by the wrong door". The attempted fix is the second half of the fault. Local-preference orders paths inside BGP; it decides which BGP path wins, and BGP then offers the RIB exactly one best path. That path still has distance 20 and still loses to the static at distance 1, so raising local-preference to 200 changed the BGP table, showed up correctly in `show ip bgp`, and could not have changed the selected route no matter how high it was set. The engineer verified the fix by looking at the layer the knob operates in, which is the one layer where the fault is not.

Remediation

Delete the static default and let BGP resolve it again — but not before understanding that this is not a free change. Every flow currently source-NATed to the ISP-B address will be translated to the ISP-A address on its next packet, so sessions in flight through the stateful path will break. Six weeks is also long enough for dependencies to accrete, so check before you commit whether anything now assumes ISP-B is the egress: firewall rules matching on an interface, allowlists at third parties, IPsec or WireGuard peers with a pinned local address. Then `delete protocols static route 0.0.0.0/0` under `commit-confirm`, in a window if the session reset matters and immediately if the allowlist rejection is costing more. Remove the local-preference change in the same commit, so nobody later reads it as evidence that path preference is being managed here. If ISP-B genuinely must stay primary for commercial reasons, express that inside BGP by raising local-preference on the ISP-B default rather than by leaving a static in place — a static default has no liveness check and will keep pointing at a provider whose transit is dead as long as the Ethernet link is up, which is the failure this incident was one outage away from.

Verification

`show ip route 0.0.0.0/0` must show the eBGP default selected via 203.0.113.1 with no static candidate present at all — not a static that has become inactive, but no static. `show ip bgp 0.0.0.0/0` should be unchanged, which is the point: it was always correct and was never the problem. `traceroute -n` from the router and from a host behind it must both leave via ISP-A, because the two resolve their route by different paths and agreeing is the evidence. Confirm the observed public source address from an external service, since that is the property the partner actually contracts on, and re-run their allowlisted call. Watch egress bytes move from `eth1` to `eth0` over the following minutes. Then verify the thing everybody skips: with the static gone, fail the ISP-A session in a window and confirm the default moves to ISP-B and returns when the session recovers. For six weeks this site had no working failover and nobody knew, so a failover that has not been exercised since the fix is a hypothesis, not a control.

Prevention

No temporary configuration without an expiry. A change made to survive a window gets its removal written into the same window's checklist, with an owner, and the window is not closed until the removal is verified — this incident is one line that outlived its reason by six weeks. Where a temporary default really is needed, make it a floating static at a distance above the dynamic source, for example 210: it is inert while BGP holds the default, it takes over automatically when BGP withdraws it, and leaving it behind is harmless. That single change of habit removes this entire failure mode. Alert on the identity of the selected default, not on its existence: the useful check is that the source of the selected 0.0.0.0/0 is BGP and its next-hop is the contracted primary, because "a default exists and traffic flows" was true throughout. Monitor egress bytes per WAN against design intent, and monitor the public source address the estate presents to the outside, which is what third parties see and what the site documents to them. And compare running configuration against the intended configuration on a schedule, so a line nobody remembers adding is found by a diff rather than by an invoice.

Reported symptoms

edge2 is the router for a regional site with two transit providers. ISP-A on eth0 is the contracted primary and sends a default over eBGP. ISP-B on eth1 is the backup and sends one too, held back by an inbound route-map. The site announces its own prefix to both.

The ticket did not come from the network team. Finance opened it, because ISP-B has spent two months billing above its 95th-percentile commit while ISP-A — the circuit the site pays a primary’s price for — bills at close to nothing.

Two more complaints arrived separately and were logged as separate problems:

  • A partner’s API started refusing the site two days ago with an IP-allowlist error. The partner had just tightened their allowlist to the single address the site documented to them years ago.
  • A handful of long-lived sessions through a stateful middlebox drop intermittently. Inbound traffic still arrives over ISP-A, so those flows are asymmetric: out one provider, back the other.

The confounding facts are what kept this open. BGP is clean — both sessions Established for weeks, prefix counts as expected, no flaps. And it has already been fixed once: a week ago an engineer raised the local-preference on the ISP-A default to 200, confirmed the change was live in the BGP table, and closed the ticket. Nothing moved.

Evidence provided

Read-only / Safetwo candidates; read which one carries the selected marker
vyos@edge2:~$ show ip route 0.0.0.0/0
Codes: K - kernel route, C - connected, S - static, R - RIP,
     O - OSPF, B - BGP, > - selected route, * - FIB route

S>* 0.0.0.0/0 [1/0] via 198.51.100.1, eth1, weight 1, 06w0d05h
B   0.0.0.0/0 [20/0] via 203.0.113.1, eth0, weight 1, 05w6d22h

Illustrative output

Read-only / Safethe deployed fix is present, correct, and the phrase to notice is 'not in RIB'
vyos@edge2:~$ show ip bgp 0.0.0.0/0
BGP routing table entry for 0.0.0.0/0
Paths: (2 available, best #1, table default, not in RIB)
64510
  203.0.113.1 from 203.0.113.1 (203.0.113.254)
    Origin IGP, localpref 200, valid, external, best (Local Pref)
64520
  198.51.100.1 from 198.51.100.1 (198.51.100.254)
    Origin IGP, localpref 50, valid, external

Illustrative output

Read-only / Safeone line, and nobody on the call put it there
vyos@edge2:~$ show configuration commands | match "static route 0.0.0.0/0"
set protocols static route 0.0.0.0/0 next-hop 198.51.100.1

Illustrative output

Read-only / Saferevision 3 is the fault; revision 4 is why it was reasonable
vyos@edge2:~$ show system commit | head -6
0   2026-08-11 09:22:04 by admin via cli   raise localpref on ISP-A default
1   2026-08-04 14:05:51 by admin via cli   firewall rule 40 for monitoring
2   2026-07-30 11:47:19 by admin via cli   ntp server change
3   2026-07-07 22:14:38 by admin via cli   temp default during ISP-A window
4   2026-07-07 21:58:02 by admin via cli   shutdown ISP-A session for window

Illustrative output

Read-only / Safefirst hop is the backup provider, from the router itself
vyos@edge2:~$ traceroute -n 198.18.0.1
traceroute to 198.18.0.1 (198.18.0.1), 30 hops max, 60 byte packets
1  198.51.100.1  1.412 ms  1.388 ms  1.401 ms
2  198.51.100.254  2.907 ms  2.884 ms  2.910 ms

Illustrative output

Work the evidence before reading on

The BGP table and the routing table disagree, and both are telling the truth.

  1. Read the two lines of show ip route 0.0.0.0/0 again. Which marker means “selected”, and which of the two candidates has it? What decides between two routes for the same prefix from two different sources?
  2. show ip bgp 0.0.0.0/0 says best next to the ISP-A path, and it also says something about the RIB. What is BGP claiming to be best among?
  3. The local-preference change is live, visible and correct. If a knob is set in the right place and has no effect, what does that tell you about which layer the decision is actually being made in?
  4. Revision 3 in the commit log was a sensible change when it was made, and the session it was made for is listed one line below it. What was supposed to happen to revision 3, and what event would have made anybody notice that it had not?

Then the question that resolves it: if you raised the ISP-A local-preference to 4,294,967,295, what would change? Your answer tells you where the fault is.

Root cause

1. A static default outlived the window it was made for

Revision 4 in the commit log shut the ISP-A session for a maintenance window. Revision 3, sixteen minutes later, added a static default toward ISP-B to keep the site online while it was down. That was correct, and it worked.

The window closed. The ISP-A session came back. The static did not go away.

2. Administrative distance decided the rest, and it knows nothing about intent

When two routes cover the same prefix, the router picks the one whose source it trusts more, and that trust is a fixed number: connected 0, static 1, eBGP 20, OSPF 110, iBGP 200. Lower wins. A static route at distance 1 beats an eBGP-learned route at distance 20 every time, in every direction, regardless of what the operator meant.

So from the moment the session recovered, the site’s entire egress moved to the backup provider. Nothing broke, because ISP-B is a working transit. There was a default; it was selected; traffic flowed. No monitoring anywhere in the estate asks whether the right traffic is leaving by the right door, so the fault surfaced through an invoice and a partner’s allowlist, eight weeks later, as two unrelated tickets.

3. Local-preference was the right idea in the wrong layer

This is the part that cost a week, and it is the most transferable thing in the scenario.

Local-preference is a BGP path attribute. It orders paths within BGP, and its job is to decide which of several BGP paths for a prefix becomes BGP’s best path. Having decided, BGP hands the RIB exactly one candidate — and that candidate arrives carrying administrative distance 20.

The contest that determines the selected route is between that one BGP candidate and the static route at distance 1. Local-preference is not an input to that contest and never sees it. Set to 200 or to four billion, it changes which BGP path is best and cannot change which route is selected.

The line to notice in the evidence is not in RIB, sitting in the same output that says best. Both are accurate. BGP is reporting that it has chosen a best path and that the RIB did not take it.

Resolution

  1. Establish what now depends on the wrong path before you correct it. Six weeks of egress via ISP-B is long enough for dependencies to accrete: firewall or NAT rules written against eth1, allowlists at third parties updated to the ISP-B address, VPN peers with a pinned local address. Correcting the route without checking turns one broken partner into several.
  2. Decide whether this is a window change or an immediate one, and say which out loud. Removing the static re-translates every source-NATed flow to the ISP-A address on its next packet, so sessions in flight break. That cost is real, and it is smaller than the allowlist rejection that is already happening — but it is a decision, not a side effect.
  3. Remove the static default and the inert fix together, under commit-confirm 5: delete protocols static route 0.0.0.0/0, and delete policy route-map RM-FROM-ISP-A rule 10 set local-preference for the knob added a week ago. Leaving the local-preference behind is worse than useless, because the next engineer will read it as evidence that path preference is managed here.
  4. Confirm the selection moved before the confirm timer expires. show ip route 0.0.0.0/0 must show the BGP route selected. If it does not, let the auto-rollback fire rather than adding another change on top.
  5. If ISP-B must remain primary for commercial reasons, do not re-add the static. Express it in BGP by raising local-preference on the ISP-B default, so the path is still tied to the liveness of the BGP session and still fails over.
  6. Re-run the partner call from the site and confirm the source address they see is the documented one. That is the symptom the business raised, and the routing table is not evidence for it.
  7. Close the loop on the window that started this: find the change record for revision 3 and record what the removal step should have said.

Verification

  1. show ip route 0.0.0.0/0 shows the eBGP default via 203.0.113.1 selected, and shows no static candidate at all. Not an inactive static — no static. A leftover inactive route is the next incident, because it also outranks the correct one the moment its next-hop becomes reachable again.
  2. show ip bgp 0.0.0.0/0 is unchanged. The BGP table was correct throughout, and a fix that changed it would mean you fixed something else.
  3. Both resolvers agree. traceroute -n from the router and from a host behind it must both leave via 203.0.113.1. Router and host resolve the route by different paths, and agreement across the two is the evidence, not either one alone.
  4. The outside world sees the right address. Check the observed public source address from an external service, and re-run the partner call that was being rejected. This is the only check independent of the router that produced the fault.
  5. Egress has moved. show interfaces ethernet eth0 and eth1 show the byte counters crossing over within a few minutes.
  6. Failover works. In a window, fail the ISP-A session and confirm the default moves to ISP-B and returns on recovery. For six weeks this site believed it had a redundant edge and had a single static route; the only way to know that has been repaired is to exercise it.

Prevention

  • Give temporary configuration an expiry. A change made to survive a window is not done when it works; it is done when it is gone. Put the removal in the same checklist, with the same owner, and do not close the window until the removal is verified.
  • When a temporary default really is needed, make it floating — a static at a distance above the dynamic source, which is inert while BGP holds the default and takes over automatically when BGP withdraws it:
set protocols static route 0.0.0.0/0 next-hop 198.51.100.1 distance 210

Left behind, that line is harmless. Left behind, the same line without distance 210 is this incident. One habit removes the entire failure mode.

  • Alert on the identity of the selected default, not its existence. “A default exists and traffic flows” was true for eight weeks. The check that would have caught this is that the selected 0.0.0.0/0 has source BGP and next-hop 203.0.113.1.
  • Monitor egress bytes per WAN against design intent, and monitor the public source address the estate presents. Third parties contract on the address, not on the routing table, and the address is observable from outside.
  • Diff the running configuration against the intended configuration on a schedule. A line nobody remembers adding should be found by a comparison, not by an invoice.