The Problem: The Hub Bottleneck in Traditional Site-to-Site VPNs
Connecting branch offices back to headquarters almost always starts with static site-to-site IPsec tunnels. You spin up a tunnel from Spoke A to the Hub, and another from Spoke B to the Hub. When branches only need central payroll or database servers, this setup works fine. But trouble starts the moment branches need to talk directly to each other—such as branch-to-branch SIP trunking, real-time database replication, or rsync backup jobs.
Say Spoke A in Frankfurt initiates a VoIP call to Spoke B in Tokyo. Every single voice packet must first cross the public internet to your central Hub in Virginia. There, the Hub decrypts the payload, checks routing tables, re-encrypts it, and pushes it back out across the Pacific to Spoke B. This hair-pinning spikes latency from an expected direct round-trip of 210 ms up to 430 ms while devouring upstream bandwidth on your Hub gateway.
You could avoid hair-pinning by building a full mesh of static IPsec tunnels between every location. But that scaling curve is brutal: O(N²). Linking just 50 branch offices requires 1,225 distinct IPsec configurations. A single IP change or subnet shift turns into an operational nightmare.
Core Concepts: Deconstructing DMVPN on Linux
Cisco developed Dynamic Multipoint VPN (DMVPN) to decouple tunnel endpoints from fixed public IP addresses. On Linux, you don’t need proprietary hardware or enterprise SD-WAN licenses. You can achieve the exact same dynamic mesh using three modular building blocks:
- Multipoint GRE (mGRE): Standard GRE creates point-to-point links, demanding a separate virtual interface for every peer. In contrast, mGRE uses a single tunnel interface (like
gre1) that dynamically terminates traffic from hundreds of arbitrary public endpoints. - Next Hop Resolution Protocol (NHRP): Handled by
opennhrp, this works like ARP for your overlay network. Spokes check in and register their public IP addresses with a Next Hop Server (NHS, typically your Hub). When Spoke A wants to push packets to Spoke B’s LAN, it asks the Hub for Spoke B’s real public IP, then negotiates a direct overlay link on the fly. - IPsec Transport Mode: We use strongSwan to encrypt all GRE traffic. Because mGRE headers already handle overlay routing and addressing, IPsec runs in lightweight Transport Mode instead of Tunnel Mode. This trims down header overhead to roughly 36 bytes per packet instead of 56+ bytes.
Hands-on Practice: Building the Lab Environment
Whether you are linking heterogeneous bare-metal regions across Hetzner and AWS or connecting remote edge gateways on dynamic IPs, this setup delivers a resilient, vendor-neutral mesh without expensive hardware.
1. Network Topology and Addressing Scheme
We’ll configure three Linux hosts running Debian 12 or Ubuntu 24.04 LTS:
- Hub (NHS): Public IP
198.51.100.1, VPN Overlay IP10.255.255.1/24 - Spoke 1: Public IP
203.0.113.10, VPN Overlay IP10.255.255.10/24, LAN192.168.10.0/24 - Spoke 2: Public IP
203.0.113.20, VPN Overlay IP10.255.255.20/24, LAN192.168.20.0/24
2. Installing Required Packages
First, install strongSwan, routing utilities, and OpenNHRP across all three instances. Then enable IPv4 packet forwarding:
sudo apt-get update
sudo apt-get install -y strongswan strongswan-pki opennhrp iproute2
# Enable packet forwarding in kernel
sudo sysctl -w net.ipv4.ip_forward=1
echo "net.ipv4.ip_forward = 1" | sudo tee -a /etc/sysctl.d/99-dmvpn.conf
# Disable reverse path filtering to prevent drops on dynamic interfaces
sudo sysctl -w net.ipv4.conf.all.rp_filter=0
sudo sysctl -w net.ipv4.conf.default.rp_filter=0
3. Configuring strongSwan for GRE Protection
Because mGRE dynamically targets spoke IPs as traffic emerges, strongSwan needs wildcard transport policies rather than rigid peer definitions. On the Hub and both Spokes, open /etc/ipsec.conf:
# /etc/ipsec.conf on Hub and Spokes
config setup
charondebug="ike 1, knl 1, cfg 1"
conn %default
keyexchange=ikev2
ike=aes256-sha256-modp2048!
esp=aes256-sha256!
type=transport
dpdaction=restart
dpddelay=30s
auto=route
conn dmvpn-gre
left=%defaultroute
leftprotoport=gre
right=%any
rightprotoport=gre
authby=secret
Set a shared Pre-Shared Key (PSK) in /etc/ipsec.secrets across all nodes:
# /etc/ipsec.secrets
: PSK "RobustSharedKeyForDMVPN_2026"
Apply the configuration: sudo ipsec restart
4. Configuring the Hub (Next Hop Server)
Create the multipoint GRE interface on the Hub using key ID 1234:
sudo ip tunnel add gre1 mode mgre key 1234 ttl 64
sudo ip addr add 10.255.255.1/24 dev gre1
sudo ip link set dev gre1 up
Next, configure /etc/opennhrp/opennhrp.conf on the Hub so it answers resolution requests without requiring static mapping files:
# /etc/opennhrp/opennhrp.conf on Hub
interface gre1
holding-time 7200
multicast dynamic
shortcut
redirect
Start the NHRP daemon: sudo opennhrp -d
5. Configuring Spoke 1 and Spoke 2
Bring up the mGRE interface on Spoke 1:
sudo ip tunnel add gre1 mode mgre key 1234 ttl 64
sudo ip addr add 10.255.255.10/24 dev gre1
sudo ip link set dev gre1 up
Now tell Spoke 1’s OpenNHRP daemon to register its WAN address with the Hub:
# /etc/opennhrp/opennhrp.conf on Spoke 1
interface gre1
map 10.255.255.1/24 198.51.100.1 register
holding-time 3600
shortcut
redirect
Replicate this configuration on Spoke 2, updating the interface address to 10.255.255.20/24. Then launch the daemon on both spokes: sudo opennhrp -d.
6. Verifying Dynamic Spoke-to-Spoke Connectivity
Verify that both spokes registered their public NBMA addresses with the Hub:
hub$ sudo opennhrpctl show
Type: local, IP: 10.255.255.1/32
Type: dynamic, IP: 10.255.255.10/32, NBMA: 203.0.113.10, flags: registered
Type: dynamic, IP: 10.255.255.20/32, NBMA: 203.0.113.20, flags: registered
Trigger traffic from Spoke 1 straight toward Spoke 2’s tunnel IP:
spoke1$ traceroute 10.255.255.20
Here is the sequence under the hood: Packet #1 traverses the Hub (10.255.255.1). OpenNHRP spots transit traffic between two spokes and sends an NHRP Redirect to Spoke 1. Spoke 1 fires an NHRP Resolution Request, learning Spoke 2’s NBMA address (203.0.113.20). Within 40 ms, strongSwan negotiates an IKEv2 SA directly between 203.0.113.10 and 203.0.113.20. Packet #2 and all subsequent traffic flow direct in 1 hop, bypassing the Hub entirely.
Conclusion
DMVPN breaks the painful compromise between operational overhead and network latency. Instead of maintaining hundreds of point-to-point tunnels, each node manages a single mGRE interface. StrongSwan offloads encryption via IPsec transport mode, while OpenNHRP builds shortcuts only when active traffic demands them.
To take this setup into production, run BGP or OSPF through FRRouting (FRR) over the gre1 overlay. That way, internal subnets behind each branch advertise and converge automatically whenever spokes establish a direct path.

