VyOSXXXVI · ECMPECMP
ECMP configuration — multiple next-hops, max-paths, load-balancing algorithm
What you'll learn
- Configure multiple next-hops for a static route
- Configure `maximum-paths` for OSPF and under the BGP address-family
- Set the kernel ECMP hash policy through `system sysctl`, and know which fields each policy hashes
- Enable BFD for ECMP path failure detection
- Diagnose unequal-cost paths and missing ECMP entries
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
ECMP configuration on VyOS 1.5 LTS / FRR 10.x differs by
protocol, and the differences are not cosmetic. Static routes
take multiple next-hops as separate next-hop arguments. OSPF
and BGP both use maximum-paths, but the node sits in a
different place in each tree and the defaults are opposite ways
round. The hashing that decides which next-hop a packet takes is
not in the routing configuration at all — it is a kernel sysctl.
This lesson is the operator’s reference for the commands, for the two places where the CLI does not offer what people expect it to, and for the production failure modes that arise when the configuration is incomplete.
Static routes with multiple next-hops
The simplest ECMP configuration: a static route with multiple next-hops.
set protocols static route 10.0.0.0/24 next-hop 192.0.2.1
set protocols static route 10.0.0.0/24 next-hop 192.0.2.2
set protocols static route 10.0.0.0/24 next-hop 192.0.2.3
commit
save
The route has three next-hops. The router keeps all three in the kernel’s FIB and distributes traffic across them via the hashing algorithm.
vyos@r1:~$ show ip route 10.0.0.0/24Routing entry for 10.0.0.0/24
Known via "static", distance 1, metric 0, best
Last update 00:02:14 ago
* 192.0.2.1, via eth0, weight 1
* 192.0.2.2, via eth0, weight 1
* 192.0.2.3, via eth0, weight 1Illustrative output
Three * lines means three next-hops are selected, not one
selected and two standing by. Confirm it in the kernel, because
that is where forwarding actually happens:
vyos@r1:~$ ip route show 10.0.0.0/2410.0.0.0/24 proto static
nexthop via 192.0.2.1 dev eth0 weight 1
nexthop via 192.0.2.2 dev eth0 weight 1
nexthop via 192.0.2.3 dev eth0 weight 1Illustrative output
Both blocks above are illustrative of the shape rather than
captures — read them for the structure (one nexthop line per
path, each with a weight) and not for the exact spacing, which
varies with the FRR and iproute2 versions in a given release.
The weight 1 on each line is the point of interest. It is not
a knob you were given: the documented sub-nodes under a static
route’s next-hop are disable, distance and bfd (with
bfd profile and bfd multi-hop source-address beneath it).
There is no per-next-hop weight, so VyOS static ECMP is
equal-cost only, and every path gets an equal share of the hash
space.
If you need weighted distribution across static next-hops, this is not the tool. The options are to move the decision into a dynamic protocol where the metric expresses the preference, or to accept equal sharing and size the links to match.
OSPF maximum-paths
OSPF installs up to maximum-paths equal-cost paths in the
routing table. Two things to unlearn here: the node sits
directly under protocols ospf, not under parameters, and
the default is 64, not 1.
set protocols ospf parameters router-id 1.1.1.1
set protocols ospf area 0 network 192.0.2.0/24
set protocols ospf maximum-paths 4
commit
save
Note the asymmetry in that block: router-id is under
parameters and maximum-paths is not. It is not a typo, and
putting maximum-paths under parameters will not commit.
Because the default is 64 (VyOS documents the range as 1-64 and
the default as MULTIPATH_NUM, which is 64), maximum-paths on
OSPF is a limit you impose, not a feature you switch on. If
OSPF ECMP is not happening, the cause is almost never this knob
— it is that the paths are not actually equal cost. Setting
maximum-paths 4 here caps the router at four paths where it
would otherwise install everything it finds; that is a
deliberate ceiling, usually chosen to match what the hardware or
the monitoring can reason about.
The validation:
vyos@r1:~$ show ip route 10.0.0.0/24Routing entry for 10.0.0.0/24
Known via "ospf", distance 110, metric 20, best
Last update 00:05:41 ago
* 192.0.2.1, via eth0, weight 1
* 192.0.2.2, via eth0, weight 1
* 192.0.2.3, via eth0, weight 1
* 192.0.2.4, via eth0, weight 1Illustrative output
Read the metric field as well as the next-hop count. All four
paths are installed because they arrived at the same metric of
20 — OSPF ECMP requires the costs to be equal, with no
relaxation available. There is no OSPF equivalent of BGP’s
multipath-relax.
So when OSPF ECMP does not happen, the fix is on the cost side:
align the interface costs (set protocols ospf interface <if> cost <n>), or check for an intermediate link whose bandwidth
gives it a different auto-cost. Raising maximum-paths will not
help, because it was already 64.
BGP maximum-paths and multipath-relax
BGP installs up to maximum-paths equal-cost paths, and unlike
OSPF the default really is single-path. The 1.5 command shape
differs from older material in three ways at once — the local AS
moved to system-as, the knob moved under the global
address-family, and there is no bare maximum-paths <n>:
set protocols bgp system-as 64512
set protocols bgp address-family ipv4-unicast maximum-paths ebgp 4
set protocols bgp address-family ipv4-unicast maximum-paths ibgp 4
commit
save
Two knobs, not three. The documented node is
address-family <ipv4-unicast|ipv6-unicast> maximum-paths <ebgp|ibgp> <1-256>;
an unqualified maximum-paths 4 has no node to land on. That is a better design than it first looks,
because eBGP and iBGP multipath are genuinely different
decisions: eBGP multipath spreads traffic across providers,
iBGP multipath spreads it across exits inside your own AS, and
an operator almost never wants the same number for both.
Note also that this is per address-family. Setting it under
ipv4-unicast does nothing for IPv6; the v6 ECMP you expected
will quietly be single-path until you set
address-family ipv6-unicast maximum-paths ebgp 4 as well.
BGP’s maximum-paths requires the paths to be “equal” by the
BGP best-path algorithm: same weight, local-preference,
AS-path length, origin, MED, and IGP cost. The operator who
wants ECMP across eBGP peers must align all of these.
multipath-relax loosens the AS-path requirement, and it lives
under parameters bestpath as-path:
set protocols bgp address-family ipv4-unicast maximum-paths ebgp 4
set protocols bgp parameters bestpath as-path multipath-relax
commit
save
With multipath-relax, two eBGP paths are considered equal
even if their AS-path lengths differ. This is the typical
configuration for ECMP across two ISPs (one with a shorter
AS-path to the destination, one with a longer).
flowchart LR
AS64512["R1 in AS 64512"]
ISP1["ISP-A in AS 64513"]
ISP2["ISP-B in AS 64514"]
DEST["10.0.0.0/24 in AS 64515"]
AS64512 -- "eBGP" --> ISP1
AS64512 -- "eBGP" --> ISP2
ISP1 -- "AS-path: 64513" --> DEST
ISP2 -- "AS-path: 64514 64516 64517" --> DEST
AS64512 -. "without multipath-relax:<br/>ISP-A only (shorter AS-path)" .-> DEST
AS64512 -. "with multipath-relax:<br/>both ISPs (relaxed)" .-> DEST
The discipline: multipath-relax is the right choice for
multi-ISP ECMP; it is not appropriate when the operator
specifically wants the shorter AS-path to win.
The kernel hashing algorithm
The kernel decides which next-hop a packet takes, and the knob
is a sysctl. On VyOS 1.5 sysctls are set through the CLI as
set system sysctl parameter <name> value <value>:
configure
set system sysctl parameter net.ipv4.fib_multipath_hash_policy value '1'
set system sysctl parameter net.ipv6.fib_multipath_hash_policy value '1'
commit
save
Read the values from the kernel documentation rather than from habit, because the naming is not what most people assume:
| Value | fib_multipath_hash_policy means |
|---|---|
0 | Layer 3 — hash over source and destination address. This is the default. |
1 | Layer 4 — the standard 5-tuple, including ports |
2 | Layer 3, or inner Layer 3 if the packet is encapsulated |
3 | Custom — the fields are chosen by fib_multipath_hash_fields |
Policy 3 is the only one that consults
fib_multipath_hash_fields, and that field is a bitmask,
not an enumeration. The kernel’s list: 0x0001 source IP,
0x0002 destination IP, 0x0004 IP protocol, 0x0010 source
port, 0x0020 destination port, with a parallel set from
0x0040 upwards for inner headers. The default is 0x0007 —
source IP, destination IP and protocol.
configure
set system sysctl parameter net.ipv4.fib_multipath_hash_policy value '3'
set system sysctl parameter net.ipv4.fib_multipath_hash_fields value '0x0033'
commit
save
0x0033 is source IP, destination IP, source port and
destination port — a 4-tuple that ignores the protocol number.
Whether that is a good idea depends entirely on your traffic;
the point is that the number is assembled from the bit list, not
picked from a menu of levels.
BFD for ECMP failure detection
The default ECMP failure detection relies on the routing protocol’s hello/keepalive (OSPF hello, BGP keepalive). The detection time is seconds. For faster detection, enable BFD:
set protocols bgp neighbor 192.0.2.1 bfd
set protocols ospf interface eth0 bfd
commit
save
Note that the BGP path carries no ASN — VyOS 1.4 moved the local
AS to set protocols bgp system-as <asn> and put peers under
set protocols bgp neighbor.
BFD detects path failure in milliseconds. The operator who deploys ECMP across links with different failure modes (e.g., a fibre link and a wireless link) should enable BFD to detect the wireless link’s intermittent failures quickly.
The validation:
show bfd peers
# Should show all ECMP next-hops as BFD peers with state Up
A clean BFD configuration: every ECMP next-hop is a BFD peer; every BFD peer reports Up state; a path failure is detected in milliseconds.
What VyOS does not expose for ECMP
It is worth being explicit about a gap, because the shape of the CLI invites an assumption that is wrong.
There is no VyOS configuration node that selects the ECMP
hashing algorithm. No set system load-balancing flow-based, no
packet-based, and no show system load-balancing. The kernel
sysctls above are the entire surface, reached through
set system sysctl parameter.
set load-balancing ... does exist, but it is a different
subsystem: the multi-WAN load balancer, which steers traffic
with connection tracking and its own per-rule interface list.
That is where per-packet-balancing lives, and it operates on
WAN rules rather than on the FIB’s equal-cost next-hop set.
Confusing the two is easy — the words overlap almost
completely — and the symptom is a configuration that commits
cleanly and does nothing to your ECMP routes.
The practical consequence for a change record: an ECMP hashing
change on VyOS is a sysctl change, and it does not appear
anywhere in the routing configuration. Anyone auditing
show configuration commands | match protocols will not see it.
Note it explicitly, or the next engineer will spend an afternoon
looking for a routing knob that does not exist.
Validation
# 1. The routing daemon installed more than one next-hop
show ip route 10.0.0.0/24
# 2. The kernel FIB agrees. This is the one that decides forwarding;
# a route with multiple next-hops in FRR and one here is a
# zebra-to-kernel problem, not a protocol problem.
ip route show 10.0.0.0/24
# 3. The hash policy is what you think it is. Read it from the
# kernel, not from the configuration: 0 is L3, 1 is L4.
cat /proc/sys/net/ipv4/fib_multipath_hash_policy
cat /proc/sys/net/ipv6/fib_multipath_hash_policy
# 4. The setting is in the configuration and will survive a reboot
show configuration commands | match fib_multipath
# 5. The traffic actually distributes. One flow proves nothing --
# a single TCP connection hashes to exactly one next-hop by design.
iperf3 -c 10.0.0.2 -P 10
# 6. Failure detection is armed on every path
show bfd peers
# 7. A failure removes the path. Do this deliberately, in a window.
show ip route 10.0.0.0/24
A clean validation: multiple next-hops in the routing table; per-flow hash; BFD enabled; traffic distributes across paths; path failure is detected.
Production failure modes
Only one next-hop installed
The operator configured two next-hops but the routing table shows one. The causes:
- Unequal costs — OSPF/BGP picks the lower-cost path. Align the costs.
- Wrong protocol knob — for BGP,
address-family ipv4-unicast maximum-paths ebgp|ibgpis unset, so the default of one path applies. For OSPF the default is 64, so this is almost never the cause there; suspect unequal cost first. - Right knob, wrong address-family — the BGP node is per address-family. A v4-only
maximum-pathsleaves v6 single-path, and everything looks configured. - Different administrative distance — for static routes, the next-hops must have the same distance.
Traffic clusters on one path
The operator sees throughput on one path much higher than the other. Causes:
- Hash clustering — few flows or biased flows. Verify with iperf3 with many parallel streams.
- Wrong hash policy — per-packet hashing on TCP. Set to per-flow.
Path failure not detected
A path fails but the routing table still shows it. The router continues to hash some flows to the failed path. Causes:
- BFD not enabled — the protocol’s hello/keepalive may take seconds to detect the failure. Enable BFD.
- No interface-state tracking — the interface is up but the path is unreachable. Use BFD or peer-tracking.
Asymmetric routing breaks stateful firewall
The forward and reverse paths use different ECMP next-hops; the stateful firewall in the middle drops the reverse packets. See Part XXXVI-01 for the detailed scenario.
Rollback
# Capture the running configuration
show configuration commands | save /tmp/ecmp-config-$(date +%s).txt
# Compare
compare
# Remove the second next-hop (revert to single-path)
delete protocols static route 10.0.0.0/24 next-hop 192.0.2.2
commit
# Or drop the BGP multipath knobs
delete protocols bgp address-family ipv4-unicast maximum-paths ebgp
delete protocols bgp parameters bestpath as-path multipath-relax
commit
Deleting protocols ospf maximum-paths is not a rollback to
single-path: the default is 64, so removing your cap widens the
ECMP set rather than closing it. If the intent is to stop OSPF
ECMP, set set protocols ospf maximum-paths 1 explicitly.
The sysctl is its own rollback path and is easy to forget:
delete system sysctl parameter net.ipv4.fib_multipath_hash_policy
restores the kernel default of 0, and it is a separate commit
from anything under protocols.
The rollback reverts to single-path routing. The traffic returns to the original next-hop.
The disciplined rollback uses commit-confirm:
commit-confirm 5
# If the ECMP change has unintended consequences, the auto-rollback
# fires after 5 minutes.
Production discipline
Cross-course references
- Part XII-03 (
XII-VyOS-StaticRouting/ static route options) covers the static route configuration including distance and next-hop options. - Part XVIII-06 (
XVIII-VyOS-OSPFFund/ SPF and cost) covers OSPF cost and the equal-cost requirement for ECMP. - Part XXVII-05 (
XXVII-VyOS-BGPBestPath/ IGP cost tiebreak) covers BGP’s equal-cost requirement and the role of IGP cost in the best-path algorithm. - Part XXXII (
XXXII-VyOS-BFD) covers the BFD configuration for fast failure detection. - Part XXXVI-01 (
XXXVI-VyOS-ECMP/ concept) covers the conceptual model. - Part XXXVI-03 (
XXXVI-VyOS-ECMP/ BGP) covers the BGP- specificmaximum-pathsandmultipath-relaxsettings. - Part XXXVI-05 (
XXXVI-VyOS-ECMP/ fwmark) covers the policy-based ECMP with fwmark.
Quiz
Knowledge check · 4 questions
Q1. An operator configures two next-hops for a static route to 10.0.0.0/24 but `show ip route 10.0.0.0/24` shows only one next-hop. What is the most likely cause?
Q2. BGP `multipath-relax` is the right configuration when the operator wants ECMP across two ISPs with different AS-path lengths to the destination.
Q3. An operator deploys ECMP across a fibre link and a wireless link. The fibre link fails but the ECMP hash continues to send flows to the wireless link for several seconds before OSPF detects the failure and removes it. During those seconds, the TCP flows on the wireless link experience retransmissions and timeouts. What is the fix?
R1 has two OSPF ECMP paths to 10.0.0.0/24: one via the fibre link (192.0.2.1) and one via the wireless link (192.0.2.2). The fibre link fails. OSPF's hello interval is 10 seconds; the dead interval is 40 seconds. The wireless link continues to be advertised; the ECMP hash continues to send flows to the fibre link for up to 40 seconds. The TCP flows on the fibre link experience retransmissions and timeouts.
Q4. R1 has two eBGP peers (ISP-A in AS 64513, ISP-B in AS 64514). Both advertise 10.0.0.0/24. ISP-A has AS-path `64513`; ISP-B has AS-path `64514 64516 64517`. The operator wants ECMP across both peers but `show bgp ipv4 10.0.0.0/24` shows only ISP-A. What is the fix on VyOS 1.5?
R1 in AS 64512 has two eBGP peers: ISP-A in AS 64513 (next-hop 192.0.2.1) and ISP-B in AS 64514 (next-hop 192.0.2.2). Both peers advertise 10.0.0.0/24. ISP-A's path is `64513`; ISP-B's path is `64514 64516 64517`. The AS-path lengths differ (1 vs 3). BGP best-path picks ISP-A. The operator wants ECMP across both.
Passing score: 75%. Answers are checked in this browser.