Uplinks (multi-WAN)#
The first tab of High availability, at /ha. It is the one place to see and
change every way this router can use more than one internet connection. It edits nothing
itself: every action stages the commands an operator would type, and the Commit Bar
applies them.
VyOS has no gateway object. An uplink is any interface that carries a default route, and there are five ways an interface can get one. This page finds all five, ranks them, joins each with the live forwarding table, and tells you which one is carrying traffic right now.
The five mechanisms#
| Mechanism | Configuration | Ranking knob | Default |
|---|---|---|---|
| Static | protocols static route 0.0.0.0/0 next-hop <gw> distance <n> | distance | 1 |
| DHCP client | interfaces … address dhcp | dhcp-options default-route-distance | 210 |
| PPPoE client | interfaces pppoe <name> | default-route-distance | 1 |
| Failover | protocols failover route 0.0.0.0/0 next-hop <gw> or … dhcp-interface <if> | metric | 1 |
| Load balancing | load-balancing wan interface-health <if> | rule weights | 1 |
An interface can be an uplink through several of these at once — that is normal, and the
Why it is an uplink line in the side panel lists every source that made it one:
static-route, dhcp, pppoe, failover, load-balancing, policy-table.
Where the data comes from#
GET /api/wan, polled every 10 seconds, is one model assembled from six reads —
agent/wan.go (handleWAN, buildWANView):
| Read | Used for |
|---|---|
config interfaces | addressing, descriptions, DHCP and PPPoE options; VLAN vif children are flattened, so eth0.10 can be an uplink |
config protocols | static default routes and the whole failover subtree |
config load-balancing | members, health tests and rules |
config policy | policy-route rules and the tables they select |
config nat | which source-NAT rule masquerades behind each uplink |
show ip route | the live standing of every default route |
show interfaces <kind> <name>, per interface | link state and live addresses |
show wan-load-balance | the balancer's own verdict — only when it is configured, because the op-mode script refuses otherwise and a refusal is not a health report |
If show ip route fails, the response carries routes_error, every health verdict
becomes unknown, and a warning says the forwarding table could not be read.
The four tiles#
| Tile | Value | Sub-line |
|---|---|---|
| Uplinks | how many were found | the mode in force |
| Carrying traffic | the interface holding the selected default route | its gateway, or no selected default route |
| Health | passing over monitored | checked uplinks passing, or no health checks configured |
| Policy routing | how many policy rules exist | how many alternate tables |
The mode is computed from what is configured, not from what is working:
| Mode | Shown as | When |
|---|---|---|
none | no uplink | no interface carries a default route |
single | single uplink | exactly one does |
distance | ranked by distance, no checks | more than one, none checked or balanced |
failover | health-checked failover | at least one protocols failover entry |
load-balancing | load-balanced | load-balancing wan configured, no failover entry |
mixed | failover + load-balancing | both |
The uplinks table#
| Column | What it shows |
|---|---|
| Uplink | the interface name, with its description underneath |
| Address | static, dhcp, pppoe or unknown, plus the live addresses (falling back to the configured ones) |
| Gateway | declared for a static route, learned from the forwarding table for a DHCP client |
| Rank | the role badge and the number, with the knob that sets it as the hover title |
| Default route | selected, installed, present or absent |
| Health | passing, failing, link down, no check or unknown, with the check targets beside it |
| Source NAT | the rule number and translation, by the load balancer, or a missing badge |
| (last) | a count of notes, when the uplink has any |
Role, and how it is decided#
Uplinks that are members of a non-exclude load-balancing rule are balanced.
Everything else with a rank is sorted: uplinks that have a failover check come first,
ordered by their metric; then the rest, ordered by distance. The first is primary,
the others backup. An uplink with no distance source at all is unranked.
Checked uplinks sort ahead of unchecked ones for a concrete reason. An installed failover route is a kernel route at distance 0, so it beats every static and DHCP default route regardless of what their distances say. Among checked uplinks, the failover metric is the rank.
Default route, and the four states#
| Badge | Meaning |
|---|---|
| selected | this route is > and * in the forwarding table: traffic is leaving here |
| installed | it is * but not >: the kernel has it, another route wins |
| present | FRR knows the route and it is not in the forwarding table |
| absent | there is no default route through this interface at all |
Where an interface has several default routes, the page shows the one with the best standing — selected beats installed beats present.
Health, and where each verdict comes from#
Health is the router's own opinion. The agent probes nothing.
| Verdict | Source |
|---|---|
| link down | the kernel reports the interface DOWN. Checked first, before anything else |
| passing / failing (checked uplink) | whether the failover daemon's kernel route for this uplink is in the forwarding table right now |
| passing / failing (balanced uplink) | Status: active or failed in show wan-load-balance |
| unknown | the forwarding table could not be read, or the balancer has not reported |
| no check | the uplink has neither a failover check nor an interface-health block |
What the router says#
When the model produces any router-wide warnings, a panel above the table lists them. These are the ones it can raise:
| Message | What it means |
|---|---|
| … share distance N: that is equal-cost multipath, not failover | two unchecked uplinks have the same distance. The kernel will use both at once for new flows, which is not what "backup" means. Give the backup a higher number, or add checks |
| <iface> has no health check; its route is never withdrawn when the path breaks | raised for every uplink without one, once any uplink has one. A provider that is up at layer 2 and dead beyond it keeps carrying traffic |
| static default route via <gw> matches no interface address; it cannot be attributed to an uplink | the gateway is not on any connected subnet and is not in the forwarding table, so the page cannot say which interface it belongs to |
| failover route via <value> names no interface and matches none | the same problem on a protocols failover entry. Add interface <if> to it |
| the forwarding table could not be read (…); health is unknown | show ip route failed |
| WAN load-balancing is configured but the daemon has not reported (…) | show wan-load-balance failed. Usually the daemon is not running |
Per-uplink warnings appear in the side panel and are counted in the table's last column:
| Message | What it means |
|---|---|
| link is down | the kernel says so |
| no source NAT rule leaves through <iface>; traffic sent this way keeps its private addresses | raised when there is more than one uplink and the balancer is not writing NAT rules |
| failover route has no check target; VyOS will refuse to commit it | a protocols failover entry with no check target is invalid |
| dhcp-options no-default-route is set: on this platform the failover daemon then installs no route for a dhcp-interface entry | see below |
And the notes, which are facts rather than faults:
- the static default route via <gw> stays as the last resort: outranked by any installed failover route, used only when every checked route is withdrawn.
- the <dhcp|pppoe> client's own default route (distance N) stays as the last resort: same reasoning.
Both exist because the obvious tidy-up — deleting the client's own route once a failover check is in place — is the thing that breaks it.
The uplink side panel#
Clicking a row opens it. Live at the top: link, addresses, gateway, the default route's standing in words, the route's protocol and distance as FRR reported them, health, and Why it is an uplink. Warnings then notes below that.
Distance#
One field, pre-filled with the current rank. What it writes depends on how the uplink gets its address:
set interfaces ethernet eth1 dhcp-options default-route-distance 220
set interfaces pppoe pppoe0 default-route-distance 20
set protocols static route 0.0.0.0/0 next-hop 203.0.113.1 distance 20The field is disabled when the uplink has no distance source, and when its rank comes from a failover metric — because then the metric is the rank, and the metric is edited in the health-check editor below.
For a VLAN uplink the interface path is rebuilt as
interfaces <kind> <parent> vif <id>, so eth0.10 writes to the right node.
Health check#
+ Add, or Edit when one exists, opens the failover editor. The node it writes is
protocols failover route 0.0.0.0/0 followed by either next-hop <gateway> — for a
static uplink — or dhcp-interface <interface> for a DHCP one. An uplink with neither
a gateway nor DHCP addressing cannot have a check, and the editor says so: This uplink
has no gateway to check through. Give it a static default route first.
| Field | Command written | Default it omits |
|---|---|---|
| Check targets | … check target <addr>, one per target | — |
| Type | … check type tcp or … check type arp | icmp is the default and is not written |
| Port | … check port <n> | written only for tcp, where it is required |
| Every (seconds) | … check timeout <n> | 10 is not written; the hint is 1–300 |
| Metric | … metric <n> | — |
| Verdict | … check policy all-available | any-available is not written |
For a next-hop entry the editor also writes … next-hop <gw> interface <iface>, which
is what lets the agent attribute the entry to an interface later.
A worked result:
set protocols failover route 0.0.0.0/0 dhcp-interface eth1 check target 192.0.2.10
set protocols failover route 0.0.0.0/0 dhcp-interface eth1 check target 198.51.100.10
set protocols failover route 0.0.0.0/0 dhcp-interface eth1 check timeout 5
set protocols failover route 0.0.0.0/0 dhcp-interface eth1 metric 1Verdict is the difference between any target answers (the default) and all
targets answer. Two targets with any-available survives one target going away;
all-available treats one unreachable target as a dead uplink, which is stricter than
most people want from a public address.
Remove stages delete protocols failover route 0.0.0.0/0 <key> <value> and closes
the panel.
Source NAT#
When nothing masquerades behind this uplink — and the load balancer is not writing NAT rules itself — the panel says so, previews three commands and offers a button:
set nat source rule 100 outbound-interface name eth1
set nat source rule 100 translation address masquerade
set nat source rule 100 description 'Wheelhouse: masquerade behind eth1'The rule number is the next free one: 100 when no source rule is numbered 100 or above, otherwise the highest plus 10. When a rule does exist, the panel names it and links to the NAT page, where its match conditions can be edited — see Source NAT.
Stop using as uplink#
The red footer button stages everything that makes this interface an uplink, in one set, and leaves the interface itself alone:
delete protocols static route 0.0.0.0/0 next-hop 203.0.113.1
set interfaces ethernet eth1 dhcp-options no-default-route
delete protocols failover route 0.0.0.0/0 dhcp-interface eth1
delete load-balancing wan interface-health eth1Only the lines that apply are staged. Note that this is the one place the product writes
no-default-route on a DHCP interface — and it does so in the same set that removes the
failover entry, so the combination the warning above is about is never created.
It does not remove the source-NAT rule. That stays until you delete it on the NAT page.
Failover health checks#
The panel below the table lists every uplink that has a protocols failover entry, with
its metric, its check, the interval and verdict policy, and a badge reading route
installed or route withdrawn. Remove check on a row stages the delete.
With no entries it says so in one line: No uplink has a health check. Open an uplink and add one.
How the mechanism behaves, in one paragraph: the failover daemon installs a default route for an uplink while its check targets answer through that uplink, and withdraws it when they stop. Among the installed ones the lowest metric wins. The route it installs is a kernel route at distance 0, so while it is there it beats every static and client-installed default route; those are the last resort, used only when every checked route has been withdrawn.
Verified on a dual-WAN VM on 2026-09-02: the daemon adds ip route … proto failover,
FRR lists it as K>* 0.0.0.0/0 [0/<metric>], and cutting a link turns that uplink to
link down / route withdrawn within the check interval while the other keeps the
selected route — docs/multi-wan.md.
Load balancing#
load-balancing wan spreads new connections across member uplinks by weight and
drops a member whose interface-health tests fail. Its verdicts come from
show wan-load-balance.
The five options#
Each is a checkbox that stages a set or a delete immediately — there is no editor
around them.
| Checkbox | Path | What it does |
|---|---|---|
| Sticky inbound connections | load-balancing wan sticky-connections inbound | replies leave through the uplink the connection arrived on |
| Do not write source NAT | load-balancing wan disable-source-nat | keep your own NAT rules; the balancer masquerades per uplink otherwise |
| Balance router-originated traffic | load-balancing wan enable-local-traffic | off by default: only forwarded traffic is balanced |
| Flush conntrack on state change | load-balancing wan flush-connections | existing flows move immediately when an uplink fails or returns |
| Prefer specific routes | load-balancing wan only-default-route | only traffic that would use the default route is balanced |
Whether the balancer writes NAT is what the uplinks table's Source NAT column reads to decide between by the load balancer and a missing badge.
Interface health#
One line per balanced uplink: its nexthop, its tests, fail after N · recover after N
(from failure-count and success-count, both defaulting to 1), the daemon's status
badge with its failure count, and when the status last changed. An uplink with no report
shows no report rather than a green badge.
Rules#
| Column | Meaning |
|---|---|
| Rule | the rule number |
| From | inbound-interface |
| Match | protocol, source address and port, destination address and port — or any |
| Uplinks | each member with its weight, or excluded from balancing |
| flags | failover and per-packet badges |
Delete stages delete load-balancing wan rule <n>.
A configuration with health tests and no rules balances nothing, and the empty table says exactly that: Health tests exist but no rule uses them; nothing is balanced.
Adding a rule#
+ Add balancing rule builds one rule and, for any member that does not have an interface-health block yet, the health test that member needs — because the balancer refuses a member without one.
set load-balancing wan rule 10 inbound-interface eth0
set load-balancing wan rule 10 interface eth1 weight 3
set load-balancing wan interface-health eth1 nexthop dhcp
set load-balancing wan interface-health eth1 test 10 target 192.0.2.10
set load-balancing wan rule 10 interface eth2 weight 1
set load-balancing wan rule 10 failover
set load-balancing wan rule 10 description 'general traffic'The nexthop it writes is the literal dhcp for a DHCP uplink and the gateway address
otherwise. The test target defaults to the uplink's gateway, and the field's tooltip
says what that is worth: the gateway is a weak one. A gateway that answers proves the
first hop is alive and nothing beyond it. Point the test at something on the far side of
the provider.
The other controls: Exclude matching traffic from balancing writes
… rule <n> exclude and nothing else, which keeps matching traffic on the main routing
table; Failover writes … failover, moving flows off a member whose tests fail;
Balance per packet rather than per connection writes … per-packet-balancing, and
its hint is the warning — it breaks anything that expects a stable source address, which
is most things.
Remove load balancing in the panel header stages delete load-balancing wan, the
whole subtree.
Policy routing#
"Send the cameras out through the LTE modem" is not a ranking question, so it is not
distance. It is a policy route rule that marks matching packets for an alternate
routing table whose only route is that uplink's gateway.
The panel lists the alternate tables — protocols static table <n> — with their routes
and the uplink each resolves to, and then each policy route <name> with the interfaces
it is bound to and its rules.
+ Route traffic via an uplink builds both halves. It creates the table only if no existing table already resolves to the chosen uplink:
set protocols static table 10 route 0.0.0.0/0 dhcp-interface eth2
set policy route WAN-POLICY rule 10 source address 10.0.5.0/24
set policy route WAN-POLICY rule 10 set table 10
set policy route WAN-POLICY interface eth0For a static uplink the table route is next-hop <gateway> instead. An uplink that is
neither DHCP-addressed nor has a known gateway produces no commands at all.
Table numbers are suggested in tens from 10 to 200, falling back to any free number in 1–200; rule numbers are the highest existing plus 10, or 10. The Applies to traffic entering on list is the router's LAN interfaces — every configured interface that has an address, is not an uplink, and is not a WireGuard, tunnel or VTI interface. When there are none, the panel says so: No LAN interface with an address; the policy would bind to nothing.
The rule needs at least one match — a source, a destination, a protocol or a destination port — or nothing is staged.
Adding an uplink#
+ Add uplink takes an interface that is not already an uplink and stages, in one go: the addressing, a distance that keeps it behind the current uplinks, an optional failover check, and a masquerade rule.
The Interface menu lists candidates: every configured interface that is not already an uplink, excluding loopbacks and dummies. Its hint shows the addresses that interface currently holds.
DHCP#
set interfaces ethernet eth2 address dhcp
set interfaces ethernet eth2 dhcp-options default-route-distance 220Static#
Both the address and the gateway are required.
set interfaces ethernet eth2 address 203.0.113.2/24
set protocols static route 0.0.0.0/0 next-hop 203.0.113.1 distance 20PPPoE#
The username and password are required. The PPPoE interface is created over the physical
one; its default name is pppoe0.
set interfaces pppoe pppoe0 source-interface eth2
set interfaces pppoe pppoe0 authentication username <user>
set interfaces pppoe pppoe0 authentication password <secret>
set interfaces pppoe pppoe0 default-route-distance 20The password field's hint is a plain statement of fact: it is stored in the router's
configuration, which is where PPPoE credentials have to live. It is a password leaf, so
GET /api/config blanks it for anyone below admin —
agent/security.go.
The rest of the panel#
Distance is pre-filled with a suggestion: the highest existing distance plus 10, or 210 for a first DHCP uplink and 1 otherwise. Description is optional and is written to whichever interface node was created.
Masquerade behind it is ticked by default and adds two commands using the next free source rule number:
set nat source rule 110 outbound-interface name eth2
set nat source rule 110 translation address masqueradeWithdraw its route when a check target stops answering adds a protocols failover
entry with the targets you list, ICMP type, a 10-second interval, any-available, and a
metric equal to the distance capped at 255.
Everything is previewed as commands before it is staged.
What this page will not do#
- It probes nothing. Every health verdict is the router's own.
- It does not reorder or renumber existing rules.
- It does not touch the firewall. A second WAN interface needs the same input policy as the first. That is the Firewall page's job, and forgetting it is how a backup uplink ends up being the open one.
- It has no IPv6 concept. The whole model looks for
0.0.0.0/0and nothing else. - It shows no per-target latency or loss, and no history of transitions. See the platform gap above.
Worked example: a second uplink as a checked standby#
eth1 is the existing DHCP uplink. eth2 is cabled to a second provider.
- Network → High availability → + Add uplink.
- Interface
eth2; Address DHCP from the provider. - Leave the suggested distance. Description
ISP B. - Leave Masquerade behind it ticked.
- Tick Withdraw its route when a check target stops answering and give it two targets on the far side of the provider.
- Read the Commands block. It should contain the address, the distance, the two NAT
lines, and four or five
protocols failoverlines. - Stage uplink, then Commit-confirm, then confirm.
Now give the first uplink a check as well — a standby with a check beside a primary without one is the worst of both, because the primary's route is never withdrawn:
- Click the
eth1row, Health check → + Add, the same targets, metric1. - Set
eth2's metric to2from its own panel. - Stage and commit.
The table should now show eth1 primary, health passing, default route
selected; eth2 backup, health passing, default route installed. Both
should have a source-NAT rule. The Health tile should read 2/2 and the mode should be
health-checked failover.
To test it, take eth1's link down and watch: within the check interval eth1 should
turn to link down / route withdrawn and eth2's route should become selected. That
is the sequence the dual-WAN VM verification runs, on 2026-09-02 —
docs/multi-wan.md.
See also#
- High availability — the page these tabs belong to.
- Addressing —
dhcp,no-default-routeand the client's own default route. - Static routes — the forwarding table this page reads, and where the
kernelroute at distance 0 comes from. - VRRP — the other half of high availability: more than one router.
- Interfaces — link state, counters and MTU for the uplink interfaces.
- Source NAT — the masquerade rules this page offers to write, and everything else that can be done to them.
Checked against agent/wan.go,
ui/src/pages/Wan.tsx,
ui/src/pages/Ha.tsx,
docs/multi-wan.md,
agent/parse.go (parseRoutes),
agent/security.go,
ui/src/lib/format.ts.