Skip to main content
RunBook Academy

← All break/fix scenarios in VyOS

advancedvyos-bgp-prefix~30 min

BGP Prefix Count Grows Unexpectedly

Reported symptoms

  • Free memory on rtr-edge-1 fell from 2.6 GB to 340 MB over five days; the low-memory alert fired on day four and was closed as "gradual growth, keep watching"
  • A planned upstream link bounce that normally reconverges in seconds took just over six minutes, during which the router forwarded nothing on that path
  • The ISP-A transit bill jumped: 95th-percentile egress on a link contracted for regional traffic tripled, while ISP-B egress fell by roughly the same amount
  • SSH sessions to the router became laggy and `show` commands took seconds to return, but no interface showed errors or drops
  • Both BGP sessions have been Established continuously for eleven days - no flap, no NOTIFICATION, no adjacency event of any kind
  • The configured maximum-prefix limit on the peer never fired, which the on-call engineer read as proof that BGP was not involved

Evidence

  • · `show ip bgp summary` reports 968412 prefixes received from 192.0.2.1 (ISP-A) against 4517 from 198.51.100.1 (ISP-B); the design figure for ISP-A is about 2000
  • · `show ip route summary` totals roughly 973000 routes where the estate baseline is about 7000
  • · `show ip bgp neighbors 192.0.2.1` shows `maximum-prefix 1000000` configured and never triggered
  • · `show log frr | grep -i maximum-prefix` contains a single MAXPFX 75% threshold warning dated five days ago, in a log with no alert route attached to it
  • · `show configuration commands | match prefix-list` shows PL-ISP-A-IN ending in `rule 100` permitting `0.0.0.0/0 le 32`
  • · `show system commit` shows a revision five days old with the comment "fix partner prefix visibility ISP-A"
  • · ISP-A confirms in writing that they have not changed their advertisement policy, and their advertised-route count to this port has been stable for months
Diagnosis and resolutionclick to reveal

Root cause

The prefix boundary on the ISP-A session was never enforced by the peer. It was enforced by the local inbound prefix-list PL-ISP-A-IN, which listed the specific ranges the contract said this router would use and relied on the implicit deny at the end of the list to discard everything else. ISP-A has always offered its full view on that port; the local list was the only thing holding the session to roughly two thousand prefixes. Five days ago a ticket reported that the partner network 203.0.113.0/24 was unreachable through ISP-A. The engineer correctly identified that PL-ISP-A-IN had no rule matching it, and then, to prevent a repeat, appended `rule 100` permitting `0.0.0.0/0 le 32` instead of adding a rule for the one prefix that was missing. That single rule removed the implicit deny and admitted ISP-A's entire table into Adj-RIBs-In and then into the Loc-RIB. Every symptom follows from that: memory tracks table size, zebra is single-threaded so convergence time tracks RIB size, and a hundredfold increase in candidate paths changed the best-path winner for a large set of destinations, which is what moved the traffic and the bill. The maximum-prefix limit did not fire because it was set to 1000000 by a template - a figure five hundred times the expected contribution, which is a safety net wide enough for the failure to fall straight through.

Remediation

Restore the boundary before touching the limit, and decide the order deliberately, because a tight maximum-prefix applied while the peer is still sending a million prefixes will shut the session down on the next update - which may be the correct outcome at 02:00 and an outage at 14:00. Replace `rule 100` with a rule for the one prefix the original ticket needed, restoring the implicit deny as the boundary: delete the catch-all, add `rule 60` permitting 203.0.113.0/24, then `compare` and `commit-confirm`. Re-apply the corrected filter to the routes already in the table with `clear ip bgp 192.0.2.1 soft in`, which uses route-refresh and does not reset the session; a hard `clear ip bgp 192.0.2.1` would withdraw every prefix from this peer and reconverge the estate for no reason. Watch the prefix count fall in `show ip bgp summary` before doing anything else. Only once the count is back near the contracted figure, tighten `maximum-prefix` on that neighbour to a number that reflects the contract plus room for legitimate growth rather than a vague ceiling, and treat the traffic shift as a separate item: confirm that egress has returned to the intended upstream rather than assuming it followed the table.

Verification

`show ip bgp summary` must show the ISP-A prefix count back at the contracted order of magnitude, not merely lower than it was; a count of 40000 is still a failure if the design figure is 2000. `show ip route summary` must return to the estate baseline, which is the check that the reduction reached the FIB and not only the BGP table. `show ip bgp 203.0.113.0/24` must still show the prefix that the original ticket was about, because a remediation that fixes the count by re-breaking the ticket it came from will be reverted by the next on-call engineer. Confirm the new maximum-prefix is capable of firing: read it back with `show ip bgp neighbors 192.0.2.1` and compare it against the observed steady-state count, and satisfy yourself that the gap is a growth margin rather than a hole. Re-check free memory and bgpd CPU after the table has settled, and re-run the link bounce test in a maintenance window to confirm convergence time has returned to seconds. Finally, confirm the 95th-percentile egress split between the two upstreams matches the intended design, from the monitoring system rather than from the router.

Prevention

The durable control is not a bigger limit, it is making the boundary visible. A prefix-list whose last rule is `0.0.0.0/0 le 32` is not a filter, and the diff that adds one is four words long and reads like a fix, so it needs to fail review rather than depend on a reviewer noticing. Record, per peer, the contracted prefix count alongside the inbound policy, and treat any deviation as a defect; that inventory is what makes a limit reviewable. Set maximum-prefix from the contracted count with a growth margin, not from an imagined worst case - a limit set far above any plausible legitimate value protects nothing, which is the failure mode this incident is made of. Route the MAXPFX threshold warning somewhere a human is paged from: the warning in this incident fired correctly on day one and went to a log file, and an unmonitored warning is the same as no warning. Alert on the prefix count itself as a monitored series with a band around the expected value, so that growth is visible before it becomes memory pressure. And keep a fast-path rule for the class of ticket that started this: when a specific prefix is missing, the remediation is a rule for that prefix, never a rule that admits everything.

Reported symptoms

Three tickets, three teams, three weeks of not talking to each other.

On Thursday the monitoring system alerted that free memory on rtr-edge-1 had crossed the low-water mark. The on-call engineer looked at a five-day graph, saw a smooth upward slope with no step in it, wrote “gradual growth, no single event, keep watching” and closed it. Smooth slopes look like leaks, and leaks are next sprint’s problem.

On Friday the network team reported that a planned bounce of the ISP-B link — routine carrier maintenance, done quarterly — took six minutes and eleven seconds to reconverge instead of the usual few seconds. Traffic on that path was black for the duration and no error counter moved.

On Monday finance reported that the ISP-A circuit, a regional peering link bought for regional traffic, had tripled its 95th-percentile egress over the weekend. ISP-B’s had fallen by roughly the same volume. No change ticket explains a traffic-engineering decision that weekend, because nobody made one.

Underneath all three, the things that did not happen matter more:

  • Both BGP sessions have been Established for eleven days. No flap, no NOTIFICATION, no adjacency event.
  • The maximum-prefix limit configured on the ISP-A neighbour has not fired. The on-call engineer read that as evidence that BGP was not involved and went looking at the hypervisor.
  • ISP-A, asked directly, confirmed in writing that they have not changed their advertisement policy in months. They are telling the truth.

The estate

rtr-edge-1 is a VyOS 1.5 LTS router in AS 64512 with two upstreams and a deliberately small routing table:

  • ISP-A, peer 192.0.2.1, AS 64500. A regional peering arrangement. The contract describes about 2,000 prefixes.
  • ISP-B, peer 198.51.100.1, AS 64600. Partial transit plus a default route, about 4,500 prefixes.

The design figure for the whole table is around 7,000 prefixes and the router is sized for it: a 4 GB virtual machine. That is an ordinary shape for a regional edge router, and it is why this failure is severe. A box built for a million routes absorbs another million; a box built for seven thousand does not.

Inbound policy on each session is a prefix-list. PL-ISP-A-IN enumerates the ranges the contract covers and relies on the implicit deny at the end of the list for everything else — the normal and correct idiom, and also the part of the design that is invisible in the configuration, because an implicit deny is not written anywhere.

Evidence provided

Read-only / Safethe design figure for 192.0.2.1 is about 2000
vyos@rtr-edge-1:~$ show ip bgp summary
Neighbor        V    AS   MsgRcvd  MsgSent  Up/Down   State/PfxRcd
192.0.2.1       4  64500    418277     1902  11d02h32m    968412
198.51.100.1    4  64600      9114     1898  11d02h31m      4517

Illustrative output

Read-only / Safethe estate baseline is about 7000
vyos@rtr-edge-1:~$ show ip route summary
Route Source         Routes               FIB
connected            14                   14
static               6                    6
ospf                 41                   41
bgp                  972918               972918
------
Totals               972979               972979

Illustrative output

Read-only / Safethe limit is real, and it is five hundred times the expected value
vyos@rtr-edge-1:~$ show ip bgp neighbors 192.0.2.1
BGP neighbor is 192.0.2.1, remote AS 64500, local AS 64512, external link
BGP state = Established, up for 11d02h32m
For address family: IPv4 Unicast
Inbound path policy configured
Incoming update prefix filter list is PL-ISP-A-IN
Maximum prefixes allowed 1000000
Threshold for warning message 75%
968412 accepted prefixes

Illustrative output

Read-only / Safeone line, five days old, in a log with no alert attached
vyos@rtr-edge-1:~$ show log frr | grep -i maximum-prefix
%BGP-4-MAXPFX: 192.0.2.1 (64500): 750001 prefixes, 75% of maximum 1000000

Illustrative output

Read-only / Safethe head and tail of the list - read rule 100
vyos@rtr-edge-1:~$ show configuration commands | match PL-ISP-A-IN
set policy prefix-list PL-ISP-A-IN rule 10 action 'permit'
set policy prefix-list PL-ISP-A-IN rule 10 prefix '198.51.100.0/24'
set policy prefix-list PL-ISP-A-IN rule 20 action 'permit'
set policy prefix-list PL-ISP-A-IN rule 20 prefix '198.51.100.0/22'
set policy prefix-list PL-ISP-A-IN rule 20 le '24'
[... rules 30 to 90 enumerate the remaining contracted ranges ...]
set policy prefix-list PL-ISP-A-IN rule 100 action 'permit'
set policy prefix-list PL-ISP-A-IN rule 100 prefix '0.0.0.0/0'
set policy prefix-list PL-ISP-A-IN rule 100 le '32'

Illustrative output

Read-only / Saferevision 1 is five days old
vyos@rtr-edge-1:~$ show system commit
0   2026-08-15 09:41:02 by vyos via cli   firewall: add NTP source rule
1   2026-08-10 16:22:47 by vyos via cli   fix partner prefix visibility ISP-A
2   2026-07-29 11:08:13 by vyos via cli   ospf: passive default on WAN
3   2026-07-14 08:55:31 by vyos via cli   monitoring: add snmp community

Illustrative output

Work the evidence before reading on

Everything above is consistent with a healthy BGP process. That is the difficulty. Nothing is broken; something is permitted that was not permitted before.

  1. Compare the two State/PfxRcd figures against the two contracts. One is off by a factor of five hundred. Which of the three tickets does that number explain, and which does it not?
  2. The limit is 1,000,000 and the count is 968,412. Ask what that limit was protecting against, and whether any misconfiguration ISP-A is capable of would have crossed it.
  3. show system commit names a change five days old. Read its comment as what the engineer intended, then read rule 100 as what they did.
  4. Put the commit, the MAXPFX warning, the memory alert and the traffic shift on one time axis and see which came first.

Before continuing: the upstream did not change what it sends. What changed about what this router is willing to accept, and where in the configuration is the thing that used to say no?

Root cause

1. The boundary lived in the local prefix-list, not at the peer

ISP-A has always offered its full view on that port. The 2,000-prefix figure in the contract describes what this router is entitled to use, not what arrives on the wire, and no mechanism at ISP-A’s end was enforcing it. The enforcement was entirely local: PL-ISP-A-IN permitted the contracted ranges, and everything else hit the implicit deny at the end of the list and was discarded.

That is the correct idiom, and it is structurally fragile in one specific way: the rule doing the work is not written down. A reviewer reading PL-ISP-A-IN sees a list of permits. The deny that turns those permits into a boundary exists only in the semantics of prefix-lists — a list with a single permit rule accepts only what that rule matches, and everything else is rejected by the implicit deny.

2. One rule removed the boundary, while fixing a real ticket

The commit five days ago was a response to a genuine fault: the partner network 203.0.113.0/24 was unreachable through ISP-A. The diagnosis was right — PL-ISP-A-IN had no rule matching it, so the route was arriving from ISP-A and being discarded before it reached the Loc-RIB.

The remediation was one rule too broad. Instead of adding a rule for 203.0.113.0/24, the engineer appended a catch-all:

set policy prefix-list PL-ISP-A-IN rule 100 action 'permit'
set policy prefix-list PL-ISP-A-IN rule 100 prefix '0.0.0.0/0'
set policy prefix-list PL-ISP-A-IN rule 100 le '32'

0.0.0.0/0 le 32 matches every IPv4 prefix of every length. The partner route appeared, the ticket was verified as fixed and closed, and the implicit deny that had been the boundary for years stopped being reachable. From that commit onward, everything ISP-A offers is accepted.

3. Every symptom is a consequence of table size

Once the table is a hundred and forty times larger than the design, the three tickets stop looking unrelated:

  • Memory. BGP memory tracks the number of paths held. The slope is smooth because the table filled as the peer’s updates arrived and were re-advertised through the update groups, not because anything leaked.
  • Convergence. zebra is single-threaded: every RIB update flows through one thread regardless of how many cores the router has. A reconvergence that walks nine hundred thousand routes takes minutes where one that walks seven thousand takes seconds. Adding vCPUs would not have helped, which is why the capacity theory produced no fix.
  • Traffic and the bill. Before the commit most destinations had exactly one candidate path, via ISP-B. Afterwards they have two, and the best-path algorithm chooses between them: where ISP-A offers a shorter AS path it wins at step 4 and the egress moves. Nobody made a traffic-engineering change; the candidate set changed.

Resolution

  1. Decide the order and the owner before typing. A tight maximum-prefix applied while the peer is still sending nearly a million prefixes shuts the session down on the next update - acceptable in a maintenance window, an outage in business hours. Fix the filter first, the limit second.
  2. Capture the before state into the ticket: show ip bgp summary, show ip route summary, and show ip bgp 203.0.113.0/24. The last one is the prefix the original ticket was about, and you must not re-break it.
  3. Replace the catch-all with the rule the original ticket needed: delete policy prefix-list PL-ISP-A-IN rule 100, then set policy prefix-list PL-ISP-A-IN rule 60 action permit and set policy prefix-list PL-ISP-A-IN rule 60 prefix 203.0.113.0/24.
  4. Read the diff with compare, then commit-confirm 10. This is inbound policy on a transit session; if the new list is wrong in the other direction you lose routes rather than gain them, and the timer is what gets them back.
  5. Re-apply the corrected filter with clear ip bgp 192.0.2.1 soft in. That uses route-refresh and does not reset the session. A hard clear ip bgp 192.0.2.1 would withdraw every prefix from this peer and reconverge the estate for no reason.
  6. Watch the count fall in show ip bgp summary, and give the FIB time to drain - the BGP table shrinks first and the kernel follows. Confirm and save once the count is at the contracted order of magnitude and 203.0.113.0/24 is still present.
  7. Only now re-derive the limit, from the contracted count plus a growth margin rather than from an imagined ceiling, and commit it as a separate change with its own comment so it is reviewable on its own.
  8. Treat the traffic shift as a separate work item. Confirm from the monitoring system that egress has returned to the intended upstream; do not infer it from the size of the routing table.

The configuration form for the limit follows the current VyOS tree, where the local ASN sits under system-as and per-peer settings sit under neighbor:

set protocols bgp system-as 64512
set protocols bgp neighbor 192.0.2.1 address-family ipv4-unicast maximum-prefix 4000

Verification

  1. The prefix count is back at the contracted order of magnitude. show ip bgp summary shows a figure near 2000 for 192.0.2.1. A count of 40000 is still a failure - "lower than it was" is not the check.
  2. The reduction reached the FIB. show ip route summary is back at the estate baseline. The BGP table and the kernel FIB are different tables and only one of them forwards packets.
  3. The original ticket is still fixed. show ip bgp 203.0.113.0/24 shows the prefix and show ip route 203.0.113.0/24 shows it installed. A fix that quietly reverts the ticket it descends from will be undone by whoever reads that ticket next.
  4. The new limit is capable of firing. Read it back with show ip bgp neighbors 192.0.2.1 and compare it against the observed steady-state count; satisfy yourself that the gap is a growth margin and not a hole.
  5. The host recovered: free memory and bgpd CPU are back in their pre-incident bands, measured after the table has settled rather than during the drain.
  6. Convergence is fast again. Re-run the link bounce in a maintenance window and time it. This is the only check that closes the second ticket, and it is the one most likely to be skipped.
  7. The egress split matches the design, read from the monitoring system over a full business day rather than from the router.
  8. The MAXPFX warning now reaches a person. Confirm the delivery path end to end rather than confirming that the log line exists.

Prevention

  • Make the boundary visible. A prefix-list whose last rule is 0.0.0.0/0 le 32 is not a filter. The diff that adds one is four lines long and reads like a fix, so it needs to fail review mechanically rather than depend on a reviewer noticing.
  • Derive the limit from the contract, not from fear. Record the contracted prefix count per peer alongside the inbound policy name and the configured maximum-prefix. That inventory is what makes a limit reviewable; without it any number looks defensible.
  • Alert on the prefix count as a series. The count per peer is the highest-value BGP number to monitor, and a band around the expected value catches growth days before it becomes memory pressure.
  • Give the threshold warning a destination. The 75% warning fired correctly here and changed nothing, because it went to a file.
  • Fix the ticket you have. When one prefix is missing, the remediation is a rule for that prefix. “So this cannot happen again” is the sentence that precedes most catch-alls, and it is worth treating as a prompt to slow down rather than to widen.
  • Size the router for the table you intend to hold, then hold it there. A 4 GB edge router with a 7,000-prefix design is sound. It stops being sound the moment nothing enforces the 7,000.