VyOSXLIII · VPN RoutingVPN Routing
BGP over VPN — iBGP over WireGuard or IPsec, peer addresses, route reflection
What you'll learn
- Configure iBGP and eBGP over a VPN tunnel on VyOS 1.5
- Decide between tunnel-address peering and loopback peering, and know when update-source is mandatory
- Configure route reflection for hub-and-spoke VPNs
- Recognise the production failure modes of BGP over VPN
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
BGP is the standard dynamic routing protocol for VPN tunnels. It is unicast-only (matching WireGuard and IPsec VTI, neither of which carries multicast), it supports arbitrary ASN configurations (allowing different ASNs at each site), and it scales to many peers. iBGP over a VPN (with all sites in the same AS) is the typical hub-and-spoke pattern; eBGP over a VPN (with different ASNs at each site) is the typical cross-domain pattern.
This lesson covers BGP over WireGuard and IPsec VTI, iBGP and eBGP configurations, what update-source actually does, route reflection for hub-and-spoke, and the production failure modes.
iBGP vs eBGP over VPN
The two flavours of BGP for VPN deployments:
iBGP over VPN (same AS at both ends):
- Both sites are in the same AS (e.g., AS 65001).
- The route learned at one site is propagated to the other without AS-path modification.
- Useful when the network is a single domain with sub-domains at each site.
- Typical pattern: hub-and-spoke with hub at data center.
eBGP over VPN (different AS at each end):
- Each site has its own AS (e.g., R1 in AS 65001, R2 in AS 65002).
- The route’s AS-path is modified at each BGP session.
- Useful for cross-domain networks (different organisations, different security domains).
- Typical pattern: site-to-site between different ASNs.
flowchart TB
subgraph "iBGP over VPN"
HUB["Hub<br/>AS 65001"]
SPOKE1["Spoke A<br/>AS 65001"]
SPOKE2["Spoke B<br/>AS 65001"]
HUB -- "iBGP" --> SPOKE1
HUB -- "iBGP" --> SPOKE2
end
flowchart LR
subgraph "eBGP over VPN"
S1["Site A<br/>AS 65001"]
S2["Site B<br/>AS 65002"]
S1 -- "eBGP" --> S2
end
The choice depends on the organisational structure. Single-organisation networks typically use iBGP; multi-organisation or multi-domain networks use eBGP.
Configure iBGP over WireGuard
A WireGuard tunnel on VyOS 1.5 carries named peers. The peer name is a label you choose; the remote endpoint is address plus port, not a single host:port string:
configure
# WireGuard tunnel on R1. Generate the keypair first with
# `generate pki wireguard key-pair` and paste the private half here;
# the peer gets the public half.
set interfaces wireguard wg0 private-key 'AI9Y7cJ0kSg1r1tLZ4kX0aM2q7uB6nD8pE3fH5jK9wU='
set interfaces wireguard wg0 address '10.10.10.1/30'
set interfaces wireguard wg0 port '51820'
set interfaces wireguard wg0 peer R2 public-key 'cVn4T2sM8xQ6yB1hJ0dR7kL3pW9zA5eG2uY8iO4nX1c='
set interfaces wireguard wg0 peer R2 address '203.0.113.2'
set interfaces wireguard wg0 peer R2 port '51820'
set interfaces wireguard wg0 peer R2 allowed-ips '0.0.0.0/0'
# iBGP over the tunnel
set protocols bgp system-as 65001
set protocols bgp parameters router-id '10.0.0.1'
set protocols bgp neighbor 10.10.10.2 remote-as '65001'
set protocols bgp neighbor 10.10.10.2 description 'iBGP to R2 over wg0'
# Advertise the local subnets
set protocols bgp address-family ipv4-unicast network '10.0.0.0/24'
set protocols bgp address-family ipv4-unicast network '192.168.1.0/24'
commit
save
The crucial configuration:
remote-as 65001— same AS as local, so this session is iBGP.networklives underaddress-family ipv4-unicaston 1.4 and later.set protocols bgp network ...was the 1.3 form and no longer exists.allowed-ipsis WireGuard’s cryptokey routing table, not a firewall rule. A prefix that BGP later installs viawg0must also be inside this peer’sallowed-ips, or the kernel drops the packet on the way out. On a two-router tunnel with a single peer,0.0.0.0/0is the honest setting; with several peers on one interface the allowed-ips have to be disjoint, which is why the hub below uses one interface per spoke.
On the remote side (R2), the mirror image:
set interfaces wireguard wg0 address '10.10.10.2/30'
set protocols bgp system-as 65001
set protocols bgp parameters router-id '10.20.0.1'
set protocols bgp neighbor 10.10.10.1 remote-as '65001'
set protocols bgp address-family ipv4-unicast network '10.20.0.0/16'
set protocols bgp address-family ipv4-unicast network '192.168.50.0/24'
After both are committed, the BGP session establishes and the routes are exchanged. Over an IPsec VTI the BGP configuration is identical — only the interface changes, from wg0 to vti0, because a VTI is a routed interface with its own address exactly like WireGuard.
Configure eBGP over a tunnel
eBGP over a tunnel is the same shape with different ASNs:
# R1 in AS 65001
set protocols bgp system-as 65001
set protocols bgp neighbor 10.10.10.2 remote-as '65002'
set protocols bgp address-family ipv4-unicast network '10.0.0.0/24'
# R2 in AS 65002
set protocols bgp system-as 65002
set protocols bgp neighbor 10.10.10.1 remote-as '65001'
set protocols bgp address-family ipv4-unicast network '10.20.0.0/16'
eBGP sends its packets with TTL 1 and expects the peer to be one hop away. Over a tunnel the peers are one hop apart — the tunnel /30 is a connected subnet on both routers — so peering on the tunnel addresses needs nothing extra. The hop count of the underlay path the tunnel traverses is irrelevant; the encapsulated packet emerges with its TTL intact.
Two knobs change that, and they are for the loopback case:
# The peer address is not on a connected subnet: raise the TTL
set protocols bgp neighbor 10.255.0.2 ebgp-multihop '2'
# Or keep TTL 1 but stop FRR insisting the peer is directly connected
set protocols bgp neighbor 10.255.0.2 disable-connected-check
ebgp-multihop 2 is the right answer when the peer really is more than one hop away. disable-connected-check is the right answer for the common loopback-to-loopback case where the peer is one hop away but its address is not on a shared subnet.
Route reflection for hub-and-spoke
For a hub-and-spoke VPN topology with many spokes, iBGP full mesh is impractical: it needs not only N*(N-1)/2 BGP sessions but a tunnel under each of them. Route reflection solves this:
flowchart LR
HUB["Hub<br/>RR (Route Reflector)"]
SPOKE_A["Spoke A<br/>RR-client"]
SPOKE_B["Spoke B<br/>RR-client"]
SPOKE_C["Spoke C<br/>RR-client"]
HUB -- "iBGP" --> SPOKE_A
HUB -- "iBGP" --> SPOKE_B
HUB -- "iBGP" --> SPOKE_C
The hub is the route reflector. The spokes are RR clients. The hub reflects routes between spokes; each spoke has exactly one iBGP session.
On VyOS 1.4 and later the route-reflector-client flag lives under the neighbour’s address-family, because reflection is a per-AFI property — a peer can be a client for IPv4 and not for IPv6:
# Hub configuration (route reflector)
set protocols bgp system-as 65001
set protocols bgp parameters router-id '10.255.0.254'
set protocols bgp neighbor 10.10.10.2 remote-as '65001'
set protocols bgp neighbor 10.10.10.2 address-family ipv4-unicast route-reflector-client
set protocols bgp neighbor 10.10.20.2 remote-as '65001'
set protocols bgp neighbor 10.10.20.2 address-family ipv4-unicast route-reflector-client
set protocols bgp neighbor 10.10.30.2 remote-as '65001'
set protocols bgp neighbor 10.10.30.2 address-family ipv4-unicast route-reflector-client
# Spoke A configuration — a plain iBGP peer, nothing reflector-specific
set protocols bgp system-as 65001
set protocols bgp parameters router-id '10.255.0.1'
set protocols bgp neighbor 10.10.10.1 remote-as '65001'
The hub reflects routes, so each spoke learns every other spoke’s prefixes over a single session. That scales to many spokes without a full mesh — but it comes with one consequence that catches people out.
A route reflector must not rewrite NEXT_HOP when it reflects; leaving the attributes alone is what makes it a reflector rather than a router in the path. So Spoke A receives Spoke B’s prefix with Spoke B’s tunnel address (10.10.20.2) as the next-hop, and Spoke A has no route to 10.10.20.0/30. BGP marks the path inaccessible and never installs it. The session is Established, show ip bgp lists the prefix, and show ip route does not.
The clean fix is to make the next-hop resolvable: have the hub advertise the spoke tunnel subnets, so Spoke A’s route to 10.10.20.2 recurses through the hub’s own tunnel address, which is directly connected. The alternative is to have the hub rewrite the next-hop with nexthop-self — but be careful, because the plain form does not apply to reflected routes; FRR needs the force variant for that. Confirm what your release exposes by typing set protocols bgp neighbor 10.10.10.2 address-family ipv4-unicast nexthop-self ? at the CLI rather than assuming, and prefer the first fix, which needs no reflector-specific behaviour at all.
Configuration patterns and validation
# Hub-and-spoke with BGP and route reflection
# Hub — one WireGuard interface per spoke, each on its own UDP port,
# so that each peer can own a disjoint allowed-ips range.
set interfaces wireguard wg0 address '10.10.10.1/30'
set interfaces wireguard wg0 port '51820'
set interfaces wireguard wg1 address '10.10.20.1/30'
set interfaces wireguard wg1 port '51821'
set interfaces wireguard wg2 address '10.10.30.1/30'
set interfaces wireguard wg2 port '51822'
set protocols bgp system-as 65001
set protocols bgp parameters router-id '10.255.0.254'
set protocols bgp neighbor 10.10.10.2 remote-as '65001'
set protocols bgp neighbor 10.10.10.2 address-family ipv4-unicast route-reflector-client
set protocols bgp neighbor 10.10.20.2 remote-as '65001'
set protocols bgp neighbor 10.10.20.2 address-family ipv4-unicast route-reflector-client
set protocols bgp neighbor 10.10.30.2 remote-as '65001'
set protocols bgp neighbor 10.10.30.2 address-family ipv4-unicast route-reflector-client
# Guard rail: cap what any one spoke may inject
set protocols bgp neighbor 10.10.10.2 address-family ipv4-unicast maximum-prefix '500'
# Spoke A
set interfaces wireguard wg0 address '10.10.10.2/30'
set protocols bgp system-as 65001
set protocols bgp parameters router-id '10.255.0.1'
set protocols bgp neighbor 10.10.10.1 remote-as '65001'
set protocols bgp address-family ipv4-unicast network '192.168.51.0/24'
Validation, run on Spoke A for a prefix that Spoke B originates:
# Is the session up, and is it carrying anything?
show ip bgp summary
# Did the prefix arrive, and with which next-hop and RR attributes?
show ip bgp 192.168.52.0/24
# Did it get installed — this is the step that fails when the
# next-hop is unresolvable
show ip route 192.168.52.0/24
# The tunnel itself
show interfaces wireguard wg0
# End to end
ping 192.168.52.10
vyos@hub:~$ show ip bgp summaryIPv4 Unicast Summary (VRF default):
BGP router identifier 10.255.0.254, local AS number 65001 vrf-id 0
BGP table version 6
RIB entries 11, using 2112 bytes of memory
Peers 3, using 64 KiB of memory
Neighbor V AS MsgRcvd MsgSent TblVer InQ OutQ Up/Down State/PfxRcd PfxSnt
10.10.10.2 4 65001 214 217 0 0 0 01:46:12 1 3
10.10.20.2 4 65001 213 216 0 0 0 01:45:58 1 3
10.10.30.2 4 65001 0 0 0 0 0 never Active 0
Total number of neighbors 3Illustrative output
Read the last column pair, not the state word alone. A peer in Active has never established. A peer with an uptime but PfxRcd of 0 has established and is exchanging nothing, which points at policy or at an empty network statement on the far side — not at the tunnel.
On a spoke, a reflected route is recognisable by two attributes it would not otherwise carry: show ip bgp 192.168.52.0/24 on Spoke A shows Spoke B’s prefix with an Originator set to Spoke B’s router-id and a Cluster list containing the hub’s cluster-id. Their presence is the proof that reflection, not ordinary iBGP propagation, put the prefix there.
If the prefix does not appear on Spoke A at all, the hub is behaving as a plain iBGP speaker: iBGP split horizon forbids it from re-advertising a route learned from another iBGP peer, so nothing is sent. That is the signature of a missing route-reflector-client — most often because the flag was set directly on the neighbour instead of under its address-family ipv4-unicast, where 1.4 and later expect it. Confirm with show configuration commands | match route-reflector on the hub.
A clean validation: the session is Established with a non-zero PfxRcd, the route is installed via the tunnel interface, and traffic passes.
Production failure modes
BGP session does not establish
TCP port 179 is refused by the router’s own firewall, the peer’s tunnel address is not reachable, or the two ends disagree about the neighbour address.
Diagnostic: show ip bgp summary shows Active, Connect or Idle. ping the peer’s tunnel address first — if that fails the problem is the tunnel, not BGP.
Fix: BGP is terminated by the router itself, so it is the input chain that matters on VyOS 1.4 and later, not a forward rule:
set firewall ipv4 input filter rule 30 action 'accept'
set firewall ipv4 input filter rule 30 protocol 'tcp'
set firewall ipv4 input filter rule 30 destination port '179'
set firewall ipv4 input filter rule 30 inbound-interface name 'wg0'
Peering address and update-source disagree
The neighbour is configured on a loopback but the session is sourced from the tunnel address, so the far side refuses the connection.
Diagnostic: the session sits in Active or Connect and never progresses. show ip bgp neighbors 10.255.0.2 reports BGP state = Active and Connections established 0. The rejection happens on the far router, so check both ends before concluding.
Fix: add set protocols bgp neighbor 10.255.0.2 update-source '10.255.0.1', and make sure each router has a route to the other’s loopback across the tunnel.
BGP installs a route the tunnel will not carry
This is the failure specific to WireGuard, and it looks nothing like a BGP problem. BGP learns 192.168.52.0/24 and installs it via wg0, but that prefix is not inside the peer’s allowed-ips. The kernel accepts the route, the packet reaches the WireGuard device, and WireGuard drops it because no peer claims that destination.
Diagnostic: show ip route 192.168.52.0/24 shows the route installed via wg0, the BGP session is healthy, and traffic to it is silently lost. show interfaces wireguard wg0 shows the peer’s allowed-ips not covering the prefix, and the transfer counters do not move when you send traffic.
Fix: widen the peer’s allowed-ips to cover everything BGP may advertise over that tunnel, or use 0.0.0.0/0 on a single-peer interface. Treat allowed-ips as part of the routing design, not as security policy.
Recursive routing: the tunnel eats its own path
The route to the tunnel’s own remote endpoint is learned through the tunnel. The tunnel comes up, the endpoint route flips to point through it, the tunnel loses its underlay path, and the cycle repeats.
Diagnostic: the session flaps on a regular period. show ip route 203.0.113.2 shows the peer’s public address resolving via wg0 or vti0 instead of via the WAN interface.
Fix: never let BGP advertise the tunnel endpoints. Filter them at the neighbour with a prefix-list, and keep a static host route to each remote endpoint via the WAN next-hop.
Session flaps with the tunnel
The underlay drops, the tunnel stops passing traffic, and BGP waits out the hold timer before withdrawing anything.
Fix: shorten the peer’s timers deliberately — set protocols bgp neighbor 10.10.10.2 timers keepalive '10' and timers holdtime '30' — or, better, attach BFD with set protocols bgp neighbor 10.10.10.2 bfd so the failure is detected in sub-second time rather than by timer expiry. Note that BFD across a tunnel is still only as fast as the tunnel’s own liveness; see Part XXXII for the BFD peer configuration.
Maximum-prefix limit reached
A misbehaving spoke injects more prefixes than the limit allows and the session is torn down to Idle.
Diagnostic: show ip bgp summary shows the peer Idle (PfxCt); the FRR log records the limit being exceeded.
Fix: this is the guard rail working. Fix the spoke, then clear the session. Raise maximum-prefix only when the new number is a number you chose.
Rollback
# Snapshot the running configuration before touching anything.
# `save` takes a path and is a configuration-mode command.
configure
save /config/vyos-bgp-vpn-before.conf
# Review the candidate before committing
compare
# Remove the BGP neighbour
delete protocols bgp neighbor 10.10.10.2
commit
# Or reload the snapshot
load /config/vyos-bgp-vpn-before.conf
commit
The rollback removes the BGP neighbor; the BGP session tears down; the routes learned via BGP are withdrawn. Use commit-confirm 5 when the session you are changing is the one carrying your management traffic.
Production discipline
Cross-course references
- Part XLIII-01 (
XLIII-VyOS-VPNRouting/ basics) covers the routing basics. - Part XXX-01 (
XXX-VyOS-RR/ concept) covers route reflection. - Part XXIV-02 (
XXIV-VyOS-BGP/ neighbour configuration) covers BGP neighbors. - Part XXXII-03 (
XXXII-VyOS-BFD/ BFD with BGP) covers the BFD peer configuration referenced above. - Part XXXI-06 (
XXXI-VyOS-BGP/ BGP performance) covers BGP performance tuning.
Quiz
Knowledge check · 4 questions
Q1. On VyOS 1.5, what does `set protocols bgp neighbor <ip> update-source <addr>` do, and when is it required?
Q2. iBGP full mesh is required for any iBGP deployment, regardless of the number of peers.
Q3. An operator moves an iBGP session from the tunnel addresses to loopbacks so it survives a tunnel rebuild. The tunnel is up and the loopbacks ping, but the session never leaves `Active`. What is the fix?
R1 and R2 are both in AS 65001, connected by a WireGuard tunnel on 10.10.10.0/30. Each has a dummy interface: R1 dum0 10.255.0.1/32, R2 dum0 10.255.0.2/32, each advertised so the other can reach it over the tunnel. The operator reconfigured both sides to `neighbor 10.255.0.x remote-as 65001` and deleted the old tunnel-address neighbours. Pings between the loopbacks succeed. `show ip bgp summary` on R1 shows the peer in `Active`; R2 shows the same. Neither side logs a protocol error, because the connection is refused before any BGP message is exchanged: R1 sources its connection from 10.10.10.1 (the connected route to R2's loopback points out wg0), and R2 has no neighbour configured at that address.
Q4. A hub-and-spoke network has 20 spokes. The operator configures iBGP full mesh (every spoke peers with every other spoke). BGP session count grows to N*(N-1)/2 = 190 sessions. The hub router is overloaded. What is the production pattern?
Hub-and-spoke network: 1 hub + 20 spokes. The operator configures iBGP full mesh: each spoke has iBGP sessions to all 19 other spokes. Total iBGP sessions = 20 * 19 / 2 = 190, and on a hub-and-spoke VPN each of those sessions also needs a tunnel underneath it. The hub cannot carry the tunnel count; CPU is saturated; convergence is slow. The operator needs a more scalable design.
Passing score: 75%. Answers are checked in this browser.