VyOSXLIII · VPN RoutingVPN Routing
VPN routing basics — routing over tunnels, recursive routing, BGP/OSPF over VPN
What you'll learn
- Put a route onto a WireGuard or IPsec VTI tunnel using the VyOS 1.5 configuration tree
- Identify the two mechanisms that produce recursive routing and name the evidence for each
- Choose between BGP, OSPF and static routing over a tunnel on the properties that actually differ
- Configure a static fallback with an administrative distance that loses to both eBGP and iBGP
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
A tunnel that carries one LAN needs no routing protocol — a static route is shorter, more predictable and easier to reason about at 02:00. Routing over a VPN earns its complexity when there are many sites, when prefixes change often enough that editing them by hand is a source of outages, or when failover has to happen without a human.
This lesson covers how a route gets onto a tunnel on VyOS 1.5, the two distinct mechanisms that produce recursive routing (they look the same from the outside and have different fixes), how to choose between BGP and OSPF over a tunnel, and how to write a static fallback that actually behaves like one.
The tunnel interface in the routing table
Both tunnel types present a normal interface with an address, and the routing
table treats them like any other. WireGuard gives you wg0; route-based IPsec
gives you vti0.
set interfaces wireguard wg0 address '10.10.10.1/30'
set interfaces vti vti0 address '10.10.20.1/30'
A static route across either one points at the far end’s tunnel address:
set protocols static route 10.20.0.0/16 next-hop '10.10.10.2'
flowchart LR
H1["Host A<br/>10.0.0.50"]
R1["R1<br/>vti0: 10.10.20.1"]
NET["Internet<br/>(ESP or WireGuard UDP)"]
R2["R2<br/>vti0: 10.10.20.2"]
H2["Host B<br/>10.20.0.50"]
H1 --> R1
R1 -- "10.20.0.0/16 via 10.10.20.2" --> NET
NET --> R2
R2 --> H2
Dynamic routing over the tunnel
The routing protocol runs over the tunnel interface as if it were a point-to-point link. On VyOS 1.5 the two protocols are configured in different places, and both moved from where older material puts them.
BGP over a tunnel
set protocols bgp system-as '65001'
set protocols bgp parameters router-id '10.10.10.1'
set protocols bgp neighbor 10.10.10.2 remote-as '65002'
set protocols bgp neighbor 10.10.10.2 update-source '10.10.10.1'
set protocols bgp neighbor 10.10.10.2 address-family ipv4-unicast
set protocols bgp address-family ipv4-unicast network '10.0.0.0/24'
Three things older course material gets wrong here. The AS number lives under
system-as, not as a node name after bgp. The address family has to be
activated on the neighbour as well as configured globally. And network is
under address-family ipv4-unicast, not directly under bgp.
A prefix in a network statement is only advertised if it exists in the
routing table. Where the local router does not otherwise have it, VyOS’s own
documentation uses a blackhole static route at a distance that loses to
everything real:
set protocols static route 10.0.0.0/24 blackhole distance '254'
OSPF over a tunnel
Per-interface OSPF parameters live under protocols ospf interface on 1.4 and
1.5. They are not under interfaces ethernet ethN ip ospf ... any more, which
is where 1.3-era material puts them.
set protocols ospf interface vti0 area '0'
set protocols ospf interface vti0 network 'point-to-point'
set protocols ospf interface vti0 cost '10'
set protocols ospf parameters router-id '10.10.20.1'
The older set protocols ospf area 0 network 10.10.20.0/30 form still exists
and enables OSPF on whatever interfaces fall inside the prefix. Prefer the
interface form: it says which interface you meant, and it is where the
per-interface knobs live anyway.
Recursive routing
The hazard specific to tunnelled routing: the tunnel needs the routing table to reach its own far endpoint, and the routing table can be persuaded to reach that endpoint through the tunnel. When that happens the tunnel becomes a prerequisite for itself.
The classic shape is a tunnel that comes up, works for a moment, and then black-holes — because the moment it comes up it teaches the router a route that destroys the underlay it was built on.
VyOS gives you two entirely separate ways to arrive there, and they have different fixes.
Mechanism 1 — the peer’s endpoint inside allowed-ips
set interfaces wireguard wg0 peer SITE-B address '203.0.113.2'
set interfaces wireguard wg0 peer SITE-B port '51820'
set interfaces wireguard wg0 peer SITE-B allowed-ips '10.10.10.0/30'
set interfaces wireguard wg0 peer SITE-B allowed-ips '10.20.0.0/16'
That is correct. Adding allowed-ips '203.0.113.0/24' — the block containing
the peer’s own endpoint — is not, and neither is allowed-ips '0.0.0.0/0' on a
router whose endpoint is reached over the same path. WireGuard’s own
encapsulated packet is addressed to 203.0.113.2, so it now matches the peer’s
allowed-ips and is offered back to wg0 to encrypt again.
Evidence: show ip route 203.0.113.2 resolves out of wg0, and the handshake
stops completing.
Fix: take the endpoint out of allowed-ips. If the design genuinely needs a
default route over the tunnel, the endpoint needs an explicit escape — a more
specific static route pinning it to the WAN next hop:
set protocols static route 203.0.113.2/32 next-hop '198.51.100.254'
Mechanism 2 — IPsec installing routes for its own traffic selectors
This one has no equivalent in WireGuard and catches people who have only met mechanism 1.
By default strongSwan installs routes for the traffic selectors a peer negotiated. On a policy-based peer with narrow selectors that is convenient. On a route-based (VTI) peer the selectors default to everything, so the route it installs can cover the peer’s own public address — and the VyOS documentation states the requirement rather than suggesting it:
set vpn ipsec options disable-route-autoinstall
Evidence: a route toward the peer’s public address, or an unexpectedly broad
route, appearing in show ip route at the moment the tunnel establishes and
disappearing when it drops — with nothing in protocols static or any routing
protocol that would have produced it.
Fix: disable-route-autoinstall, and then put the routes in deliberately, with
static routes or a routing protocol.
The general rule
Whichever mechanism produced it, the invariant is the same: the route to the tunnel’s far endpoint must not resolve through the tunnel. Two ways to guarantee it, and mature designs use both:
- Pin the underlay. A static host route for the peer’s public address via the WAN next hop. It is more specific than anything a tunnel can teach you, so it wins on longest-prefix match regardless.
- Filter what the tunnel may teach. A prefix-list on the BGP session over the tunnel that rejects the underlay prefixes, so the recursive route cannot be learned in the first place. Pinning stops the symptom; filtering stops the route existing.
Static fallback, with a distance that actually loses
A common pattern: dynamic routing for the real path, plus static routes at a high administrative distance so the site does not go dark while BGP reconverges.
The distances that matter, as FRR implements them:
| Source | Administrative distance |
|---|---|
| Connected | 0 |
| Static (VyOS default) | 1 |
| eBGP | 20 |
| OSPF | 110 |
| iBGP | 200 |
set protocols static route 10.20.0.0/16 next-hop '10.10.10.2' distance '210'
The fallback is only a fallback if it survives the failure it exists for. A static route whose next hop is the far end’s tunnel address goes away with the tunnel — which is exactly when you wanted it. If the fallback is meant to survive the tunnel, its next hop has to be on a different path: a second tunnel, a different WAN, or an MPLS circuit.
BGP or OSPF over a tunnel
| Property | BGP | OSPF |
|---|---|---|
| Transport | TCP 179, unicast | IP protocol 89, hellos to 224.0.0.5 |
| Works over a tunnel as-is | yes | no — needs network non-broadcast plus static neighbours |
| Separate ASNs per site | yes, by design | no — one OSPF domain |
| Policy control | rich: prefix-lists, route-maps, communities, AS-path | area types and metrics |
| Failure blast radius | a session drops, its routes withdraw | an LSA change reruns SPF domain-wide |
| Scales with | number of peers | number of prefixes in one domain |
| Configuration cost | higher | lower |
For two sites and one tunnel, static routes. Adding a routing protocol to carry two prefixes buys nothing and adds a thing that can be down while the tunnel is up.
For a hub with many spokes, BGP. Each spoke is a session; a spoke that misbehaves affects its own session and not the others’ SPF.
For a small number of sites already inside one OSPF domain, OSPF over the tunnel is the lower-friction choice, provided the tunnel carries its hellos — see the WireGuard note above.
For different administrative domains at each end — a partner, an acquired network, anything where the far side changes without telling you — BGP, because policy at the boundary is the point and OSPF has no equivalent.
Validation
show interfaces vti vti0
show ip route 10.20.0.0/16
show bgp ipv4 summary
show ip ospf neighbor
show ip ospf interface vti0
Read them in that order, because each one makes the next one meaningful: an interface that is down explains a session that is down, which explains a route that is missing.
$ show ip route 10.20.0.0/16Routing entry for 10.20.0.0/16
Known via "bgp", distance 20, metric 0, best
Last update 00:14:22 ago
10.10.10.2, via vti0
Routing entry for 10.20.0.0/16
Known via "static", distance 210, metric 0
10.10.10.2, via vti0Illustrative output
Both entries present with the BGP one marked best is the state you want: the fallback exists and is losing. Only the static one present means the session is down. Only the BGP one present means there is no fallback, whatever the change ticket claimed.
Then prove it end to end, from a host and not from the router:
# Substitute your own values before running:
FAR_HOST=10.20.0.50
ping -c 3 "$FAR_HOST"
traceroute -n "$FAR_HOST"
A router-to-router ping uses the tunnel addresses and can succeed while every host behind the routers still fails, because the host path involves a different source address, a different route lookup on the far end, and a firewall the router-to-router test never touched.
Production failure modes
The BGP session will not establish over the tunnel
The neighbour sits in Active or Connect.
Diagnostic: show bgp ipv4 summary for the state; show ip route 10.10.10.2
to confirm the peer’s tunnel address resolves out of the tunnel interface;
show interfaces vti vti0 or show interfaces wireguard wg0 for the tunnel
itself.
Causes, cheapest first: the tunnel is not actually up; the peer address is not
in allowed-ips on a WireGuard tunnel, so the TCP SYN is dropped after routing;
the router’s own input firewall does not permit TCP 179 from the peer’s tunnel
address; update-source is missing, so the session sources from the WAN address
and the far end does not recognise it.
The OSPF adjacency never forms
Diagnostic: show ip ospf neighbor is empty; show ip ospf interface vti0
shows the network type and timers actually in force.
Causes, most likely first: the network type is one of the three that address
their hellos to 224.0.0.5 — broadcast, point-to-point or
point-to-multipoint — on an interface that is not registered as
multicast-capable, so nothing arrives at the far end; non-broadcast is set but
no protocols ospf neighbor entry exists, so there is nobody to unicast to;
the tunnel subnet is not in allowed-ips, so even the unicast hellos are
dropped; hello and dead intervals differ between the two routers, in which case
the capture shows hellos arriving in both directions and being rejected.
Recursive routing black-holes the tunnel
Covered above. The distinguishing evidence is that the underlay route appears and disappears with the tunnel rather than being configured anywhere.
The tunnel flaps and the routes flap with it
Every flap withdraws and re-announces the prefixes, and every withdrawal is a brief outage for everything behind them.
Diagnostic: correlate the session drops with the tunnel’s own events in
show log ipsec (IPsec) or the WireGuard handshake timestamps.
Fix the tunnel first — a routing protocol tuned to tolerate a flapping tunnel is a way of hiding the flap, not of fixing it. Then, if flaps remain: a static fallback at distance 210 so the site is not dark during reconvergence, and consideration of whether the underlay itself needs redundancy.
The MTU is wrong and only large packets notice
The routing is perfect and the tunnel drops full-size packets. Ping succeeds, transfers stall.
Fix: size the tunnel MTU for the encapsulation and clamp MSS on the tunnel
interface — set interfaces vti vti0 mtu 1400 and set interfaces vti vti0 ip adjust-mss clamp-mss-to-pmtu. Part XLIII-05 covers the arithmetic.
Rollback
compare
commit-confirm 10
A routing change over a tunnel is exactly the case commit-confirm exists for:
if it removes the route your own session is using, you cannot type the revert.
Let the window expire instead.
If you have not committed, discard. If you have confirmed:
load /config/pre-change-TICKET.conf
commit
save
To remove just what you added:
delete protocols bgp neighbor 10.10.10.2
delete protocols ospf interface vti0
commit
Production discipline
Cross-course references
- Part XLI-04 (
XLI-VyOS-WireGuard/ routing) coversallowed-ipsas a routing mechanism in depth. - Part XLII-04 (
XLII-VyOS-IPsec/ route-based VTI) covers the VTI and its binding to a peer. - Part XLIII-02 (
XLIII-VyOS-VPNRouting/ BGP over VPN) and XLIII-03 (OSPF over VPN) take each protocol further. - Part XLIII-05 (
XLIII-VyOS-VPNRouting/ VPN MTU) covers the MTU arithmetic. - Part XXIII-02 (
XXIII-VyOS-BGP/ eBGP vs iBGP) covers the distances this lesson relies on.
Quiz
Knowledge check · 4 questions
Q1. What makes a tunnel's routing recursive, and why does it break the tunnel rather than merely being inefficient?
Q2. OSPF cannot be used over a WireGuard tunnel at all, because the interface is not multicast-capable and OSPF depends on multicast.
Q3. An operator adds `set interfaces wireguard wg0 peer SITE-B allowed-ips 0.0.0.0/0` to route all branch traffic through the head office. The tunnel is up before the commit and dead a few seconds after. What happened, and how is it fixed without abandoning the full-tunnel design?
R1's WireGuard peer SITE-B has its endpoint at 203.0.113.2, reached over the branch's WAN via 198.51.100.254. Before the change, `allowed-ips` covered only the tunnel /30 and the head office prefixes. `0.0.0.0/0` now matches every destination, including 203.0.113.2. WireGuard's own encapsulated UDP datagram is addressed to 203.0.113.2, so after the route lookup it meets the `allowed-ips` check, matches SITE-B, and is handed back to wg0 to be encrypted a second time. The handshake stops completing and the tunnel dies. Nothing in `show ip route` looks unusual, because the routing table did exactly what it was told; the second filter underneath it is where the loop closes.
Q4. A branch runs iBGP over a tunnel to the data centre. The operator adds static fallback routes at distance 200 so the branch stays reachable during a flap. After the change, traffic sometimes takes the static route while the BGP session is up. Why, and what is the correct value?
FRR gives iBGP an administrative distance of exactly 200. The static routes were configured at distance 200 as well, on advice written for eBGP, whose distance is 20 and against which 200 is a genuine fallback. With both sources at 200 the comparison is a tie, and which route is installed is decided by FRR's internal ordering rather than by the operator's intent — so the behaviour can differ between reloads and between the two routers. The fallback is not failing loudly; it is winning some of the time, which is worse, because the symptom appears only when the static next hop and the BGP next hop lead somewhere different.
Passing score: 75%. Answers are checked in this browser.