You are currently viewing WireGuard Hub and Spoke BGP FRR: Dynamic Routing Across Multiple Sites

WireGuard Hub and Spoke BGP FRR: Dynamic Routing Across Multiple Sites

  • Post author:
  • Post category:Tutorials
  • Post comments:0 Comments
  • Reading time:6 mins read


Table of Contents

  1. Overview
  2. Prerequisites
  3. Quick Architecture
  4. IP Plan & Variables
  5. Install / Setup
  6. Base Configuration (WireGuard + FRR/BGP)
  7. Routing Policy & Advertisements
  8. Reload/Enable & Health Checks
  9. Security / Hardening
  10. Performance & Optimization
  11. Backup & Restore
  12. Troubleshooting (Top issues)
  13. Key Takeaways & Next Steps
  14. More reading (external)

Overview

This guide builds a production-ready WireGuard Hub and Spoke BGP FRR fabric: one Hub and multiple Spokes connected with WireGuard tunnels.
Instead of static routes, we run FRR (Free Range Routing) with BGP over the tunnels so each site auto-learns remote LANs.
You’ll generate keys, create wg interfaces per peer, stand up FRR with clean BGP configs, and verify convergence/failover.

Docs: WireGuard ·
FRR User Guide.

Prerequisites

  • 1 Hub gateway + 2 or more Spoke gateways (Linux, recent kernels).
  • Public/reachable IP or DNAT for Hub; open UDP 51820 (or your chosen port).
  • Each site has a distinct LAN prefix (e.g., Hub 10.10.0.0/16, Spoke1 10.20.0.0/16, Spoke2 10.30.0.0/16).
  • Shell access as root or via sudo on all nodes.

Quick Architecture

WireGuard Hub and Spoke BGP FRR
Hub (center) terminates one WG interface per Spoke; BGP runs over the WG overlay.

IP Plan & Variables

Copy this block into a file (e.g., /root/wg-vars.sh) on every node, then source it before running commands.

# WireGuard overlay
export WG_NET="10.99.0.0/24"
export WG_HUB_IP="10.99.0.1/24"
export WG_S1_IP="10.99.0.11/24"
export WG_S2_IP="10.99.0.12/24"
export WG_PORT=51820

# Public endpoints (edit to your DNS/IP)
export HUB_ENDPOINT="hub.example.com"
export S1_ENDPOINT="s1.example.com"
export S2_ENDPOINT="s2.example.com"

# LANs
export HUB_LAN="10.10.0.0/16"
export S1_LAN="10.20.0.0/16"
export S2_LAN="10.30.0.0/16"

# BGP
export HUB_AS=65010
export SPOKE1_AS=65021
export SPOKE2_AS=65022

Install / Setup

Run only the matching block for your OS on each node. Then enable BGP daemon in FRR.

Ubuntu/Debian

sudo apt update
sudo apt -y install wireguard iproute2 frr frr-pythontools
sudo sed -i 's/^bgpd=no/bgpd=yes/' /etc/frr/daemons
sudo systemctl enable --now frr

RHEL/Rocky/CentOS Stream/Fedora

sudo dnf -y install wireguard-tools iproute frr
sudo sed -i 's/^bgpd=no/bgpd=yes/' /etc/frr/daemons
sudo systemctl enable --now frr

Arch/Manjaro

sudo pacman -Syu --noconfirm wireguard-tools frr
sudo sed -i 's/^bgpd=no/bgpd=yes/' /etc/frr/daemons
sudo systemctl enable --now frr

openSUSE/SLE

sudo zypper refresh
sudo zypper install -y wireguard-tools frr
sudo sed -i 's/^bgpd=no/bgpd=yes/' /etc/frr/daemons
sudo systemctl enable --now frr

Enable IP forwarding (all nodes)

echo 'net.ipv4.ip_forward=1' | sudo tee /etc/sysctl.d/99-wg.conf
sudo sysctl --system | grep net.ipv4.ip_forward

Base Configuration (WireGuard + FRR/BGP)

We will configure: (1) unique keypairs per node, (2) one wg interface per neighbor on the Hub, and (3) one wg interface to the Hub on each Spoke.

Step 1 — Generate keys (on each node)

Run on Hub:

umask 077
wg genkey | sudo tee /etc/wireguard/hub.key | wg pubkey | sudo tee /etc/wireguard/hub.pub
sudo chmod 600 /etc/wireguard/hub.key
sudo cat /etc/wireguard/hub.pub

Run on Spoke1:

umask 077
wg genkey | sudo tee /etc/wireguard/s1.key | wg pubkey | sudo tee /etc/wireguard/s1.pub
sudo chmod 600 /etc/wireguard/s1.key
sudo cat /etc/wireguard/s1.pub

Run on Spoke2:

umask 077
wg genkey | sudo tee /etc/wireguard/s2.key | wg pubkey | sudo tee /etc/wireguard/s2.pub
sudo chmod 600 /etc/wireguard/s2.key
sudo cat /etc/wireguard/s2.pub

Step 2 — Exchange public keys

  1. Copy hub.pub to both Spokes.
  2. Copy s1.pub to Hub; copy s2.pub to Hub.

Step 3 — Create WireGuard interfaces

On Hub create one interface per Spoke.

/etc/wireguard/wg-s1.conf

[Interface]
PrivateKey = (contents of /etc/wireguard/hub.key)
Address = ${WG_HUB_IP}
ListenPort = ${WG_PORT}
MTU = 1420

[Peer]
PublicKey = (paste s1.pub)
AllowedIPs = ${WG_S1_IP}
Endpoint = ${S1_ENDPOINT}:${WG_PORT}
PersistentKeepalive = 25

/etc/wireguard/wg-s2.conf

[Interface]
PrivateKey = (contents of /etc/wireguard/hub.key)
Address = ${WG_HUB_IP}
ListenPort = ${WG_PORT}
MTU = 1420

[Peer]
PublicKey = (paste s2.pub)
AllowedIPs = ${WG_S2_IP}
Endpoint = ${S2_ENDPOINT}:${WG_PORT}
PersistentKeepalive = 25

On Spoke1:

/etc/wireguard/wg-hub.conf

[Interface]
PrivateKey = (contents of /etc/wireguard/s1.key)
Address = ${WG_S1_IP}
ListenPort = ${WG_PORT}
MTU = 1420

[Peer]
PublicKey = (paste hub.pub)
AllowedIPs = ${WG_HUB_IP}
Endpoint = ${HUB_ENDPOINT}:${WG_PORT}
PersistentKeepalive = 25

On Spoke2:

/etc/wireguard/wg-hub.conf

[Interface]
PrivateKey = (contents of /etc/wireguard/s2.key)
Address = ${WG_S2_IP}
ListenPort = ${WG_PORT}
MTU = 1420

[Peer]
PublicKey = (paste hub.pub)
AllowedIPs = ${WG_HUB_IP}
Endpoint = ${HUB_ENDPOINT}:${WG_PORT}
PersistentKeepalive = 25

Step 4 — FRR BGP configs

On Hub (/etc/frr/frr.conf):

frr defaults traditional
!
router bgp ${HUB_AS}
 bgp router-id 10.99.0.1
 neighbor 10.99.0.11 remote-as ${SPOKE1_AS}
 neighbor 10.99.0.12 remote-as ${SPOKE2_AS}
 ! Advertise hub LAN
 network ${HUB_LAN}
!
line vty

On Spoke1 (/etc/frr/frr.conf):

frr defaults traditional
!
router bgp ${SPOKE1_AS}
 bgp router-id 10.99.0.11
 neighbor 10.99.0.1 remote-as ${HUB_AS}
 ! Advertise spoke1 LAN
 network ${S1_LAN}
!
line vty

On Spoke2 (/etc/frr/frr.conf):

frr defaults traditional
!
router bgp ${SPOKE2_AS}
 bgp router-id 10.99.0.12
 neighbor 10.99.0.1 remote-as ${HUB_AS}
 ! Advertise spoke2 LAN
 network ${S2_LAN}
!
line vty

Routing Policy & Advertisements

Default is one LAN per site. To filter or change attributes, use prefix-lists and route-maps. Example (apply on Hub): only accept up to /24 from each Spoke.

router bgp ${HUB_AS}
 neighbor 10.99.0.11 route-map FROM-S1 in
 neighbor 10.99.0.12 route-map FROM-S2 in
!
ip prefix-list ONLY-24 permit 0.0.0.0/0 le 24
route-map FROM-S1 permit 10
 match ip address prefix-list ONLY-24
route-map FROM-S2 permit 10
 match ip address prefix-list ONLY-24

Reload/Enable & Health Checks

Step 5 — Bring up WireGuard

On Hub:

sudo systemctl enable --now wg-quick@wg-s1
sudo systemctl enable --now wg-quick@wg-s2
sudo wg show

On Spokes (each one):

sudo systemctl enable --now wg-quick@wg-hub
sudo wg show

Step 6 — Restart FRR and verify BGP

sudo systemctl restart frr
vtysh -c 'show ip bgp summary'
vtysh -c 'show ip bgp'
# You should see Hub learn S1_LAN and S2_LAN, and Spokes learn HUB_LAN.

Step 7 — Basic routing checks

# From Hub, should see routes to Spoke LANs:
ip route | grep -E '10\.20\.|10\.30\.'

# From Spoke1, should see route to Hub LAN:
ip route | grep '10\.10\.'

# Ping across sites (adjust IPs to your LAN hosts):
ping -c3 10.20.0.10 || true
ping -c3 10.10.0.10 || true

Security / Hardening

Open only the WireGuard UDP port; limit vtysh to admins; protect private keys.

Ubuntu/Debian (UFW)

sudo ufw allow OpenSSH
sudo ufw allow ${WG_PORT}/udp
sudo ufw reload
sudo ufw status

RHEL/Rocky/CentOS/Fedora/openSUSE/SLE (firewalld)

sudo firewall-cmd --permanent --add-port=${WG_PORT}/udp
sudo firewall-cmd --reload
sudo firewall-cmd --list-ports

Key hygiene: keep /etc/wireguard/*.key at 600, root-owned; never share private keys.

Performance & Optimization

  • MTU: Test with ping -M do; 1380–1420 works in most NATs.
  • Keepalives: 25s to maintain NAT bindings across multiple tunnels.
  • BGP timers: for faster convergence lower keepalive/hold but beware flaps (e.g., 10/30s).

Backup & Restore

Backup

sudo tar -C / -czf /root/wg-bgp-backup-$(date +%F).tgz   etc/wireguard etc/frr --numeric-owner
sha256sum /root/wg-bgp-backup-$(date +%F).tgz

Restore

BACKUP="/root/wg-bgp-backup-YYYY-MM-DD.tgz"
sudo tar -C / -xzf "$BACKUP"
sudo chown -R root:root /etc/wireguard /etc/frr
sudo chmod 600 /etc/wireguard/*.key || true
sudo systemctl restart wg-quick@wg-s1 || true
sudo systemctl restart wg-quick@wg-s2 || true
sudo systemctl restart wg-quick@wg-hub || true
sudo systemctl restart frr

Troubleshooting (Top issues)

No BGP session — WG up but routes missing: check AllowedIPs include peer WG IPs; verify UDP reachability and BGP ASNs.

sudo wg show
vtysh -c 'show ip bgp summary'
sudo journalctl -u frr --since "10 min ago"

Routes flap — Too-aggressive timers or MTU issues; raise BGP timers and lower MTU.

vtysh -c 'show interface wg-s1'
ping -M do -s 1372 10.99.0.11

Key Takeaways & Next Steps

  • WireGuard Hub and Spoke BGP FRR scales cleanly: add peers, advertise prefixes—no manual static routes.
  • Harden keys/firewall; tune MTU and BGP timers for stability.
  • Next: add route reflectors, multi-hub redundancy, or export metrics to Prometheus.

More reading (external)

Leave a Reply