A high-availability solution for OpenWrt routers, providing automatic failover, configuration synchronization, and DHCP lease replication.
- Virtual IP Failover - VRRP-based failover with sub-second switchover (keepalived)
- Configuration Sync - Bi-directional, encrypted sync of UCI configs between nodes (owsync)
- DHCP Lease Sync - Real-time lease replication for seamless client experience (lease-sync)
- Web Interface - LuCI integration for easy setup and monitoring
- IPv4 and IPv6 - Full dual-stack support for VIPs and sync traffic
| Package | Description |
|---|---|
ha-cluster |
Core orchestration - manages keepalived, owsync, and lease-sync |
luci-app-ha-cluster |
Web interface for configuration and monitoring |
owsync |
Lightweight encrypted file synchronization daemon |
lease-sync |
Real-time DHCP lease replication daemon |
dnsmasq-ha |
dnsmasq with ubus lease management patches |
- OpenWrt 24.10
- Two or more routers on a shared network segment
- dnsmasq-ha (for DHCP lease sync)
# Add feed to /etc/openwrt_distfeeds.conf or feeds.conf
echo "src-git hacluster https://github.com/pgaufillet/openwrt-ha-feed.git" >> feeds.conf
# Update and install
./scripts/feeds update hacluster
./scripts/feeds install -a -p hacluster# Install the complete HA solution
opkg update
opkg install ha-cluster luci-app-ha-cluster
# Or install individual components
opkg install owsync # Config sync only
opkg install lease-sync # DHCP sync onlyOn both routers:
-
Install packages:
opkg update opkg install ha-cluster luci-app-ha-cluster
-
Open LuCI: Services → High Availability → Quick Setup
-
Configure:
- Enable HA Cluster
- Set node name (e.g.,
router1,router2) - Set priority (higher = preferred master, e.g., 100 and 90)
- Add peer IP address
- Add Virtual IP for your LAN interface
- Generate and copy encryption key to both nodes
-
Save & Apply
-
Verify: Check Status tab for cluster health
Edit /etc/config/ha-cluster:
config global 'config'
option enabled '1'
option node_priority '100'
option encryption_key '0123456789abcdef...' # 64 hex chars
config peer
option name 'router2'
option address '192.168.1.2'
config vrrp_instance 'main'
option vrid '51'
option interface 'lan'
option priority '100'
option nopreempt '1'
config vip 'lan'
option enabled '1'
option vrrp_instance 'main'
option interface 'br-lan'
option address '192.168.1.254'
option netmask '255.255.255.0'
Start the cluster:
/etc/init.d/ha-cluster enable
/etc/init.d/ha-cluster start┌───────────────────────────────────────────────────────┐
│ ha-cluster │
│ (Orchestration Layer) │
└───────┬──────────────────┬──────────────────┬─────────┘
│ │ │
▼ ▼ ▼
┌─────────────────┐ ┌──────────────┐ ┌──────────────────┐
│ keepalived │ │ owsync │ │ lease-sync │
│ (VRRP) │ │ (Config Sync)│ │ (DHCP Sync) │
└─────────────────┘ └──────────────┘ └──────────────────┘
- keepalived: Manages VRRP for virtual IP failover
- owsync: Syncs UCI configuration files with AES-256-GCM encryption
- lease-sync: Replicates DHCP leases in real-time via ubus and UDP with AES-256-GCM encryption
- ha-cluster Design - Architecture and configuration details
- LuCI Application - Web interface guide
- owsync - Config sync daemon (standalone project)
- Test Infrastructure - Container-based testing
- Encryption: All sync traffic encrypted with AES-256-GCM (PSK)
- Key Management: Keys stored in config files, never exposed on command line
- Network: Designed for trusted LAN segments; use VPN for untrusted networks
What works:
- IPv6 Virtual IPs (dual-stack VIP failover)
- IPv6 sync traffic between nodes
- SLAAC (default OpenWrt) - clients self-configure, no lease sync needed
- Prefix Delegation from ISP - unaffected (odhcp6c is separate)
- PD delegation to downstream routers - works (downstream may get different prefix on failover, which is normal IPv6 renumbering behavior)
What doesn't work:
- Stateful DHCPv6 lease sync when using odhcpd (OpenWrt's default DHCPv6 server)
Why: lease-sync integrates with dnsmasq, not odhcpd. Since default OpenWrt uses SLAAC (stateless), most users are unaffected.
Recommended Workaround - Disable odhcpd: The simplest and most reliable approach for v1.0:
# Disable odhcpd, let dnsmasq handle all DHCP/RA
uci set dhcp.odhcpd.enabled='0'
uci set dhcp.lan.dhcpv6='server'
uci set dhcp.lan.ra='server'
uci commit dhcp
/etc/init.d/odhcpd stop
/etc/init.d/dnsmasq restartThis enables full DHCPv6 lease sync but loses odhcpd-specific features (PD relay, NDP proxy).
Advanced Workaround - Hybrid Architecture (UNTESTED): For experienced users who need odhcpd's unique features:
# /etc/config/dhcp - dnsmasq handles RA + DHCPv6 + DNS (lease-sync works)
config dhcp 'lan'
option interface 'lan'
option dhcpv4 'server'
option dhcpv6 'server' # dnsmasq handles DHCPv6 addresses
option ra 'server' # dnsmasq sends RA
# CRITICAL: Explicitly disable odhcpd on LAN interface
config dhcp 'lan_odhcpd'
option interface 'lan'
option ra 'disabled' # Don't send RA (dnsmasq does it)
option dhcpv6 'disabled' # Don't do DHCPv6 (dnsmasq does it)
option ndp 'hybrid' # NDP proxy only
# odhcpd global settings
config odhcpd 'odhcpd'
option maindhcp '0'This should preserve:
- ✅ DHCPv6 lease sync (via dnsmasq + lease-sync)
- ✅ NDP proxy (odhcpd)
⚠️ PD relay to downstream (works, but downstream may experience lease expiration delay on failover)
# Via CLI
ubus call ha-cluster status
# Via LuCI
Services → High Availability → Statuslogread | grep -E "(ha-cluster|keepalived|owsync|lease-sync)"| Issue | Solution |
|---|---|
| VIP not appearing | Check interface name matches UCI config |
| Sync not working | Verify encryption keys match on both nodes |
| Peer unreachable | Check firewall allows ports 4321 (owsync), 4322 (lease-sync), 112 (VRRP) |
| Split brain | Ensure reliable network between nodes |
A setup script is provided to clone and configure an OpenWrt buildroot for compiling the feed packages:
# Default setup (OpenWrt 24.10.5, x86_64 config)
scripts/setup-openwrt-buildroot.sh ../openwrt
# Build all packages
cd ../openwrt
make package/ha-cluster/compile V=s
make package/owsync/compile V=s
make package/lease-sync/compile V=s
make package/dnsmasq-ha/compile V=s
make package/luci-app-ha-cluster/compile V=sBuilt .ipk files are output to bin/packages/*/ha_feed/.
See scripts/setup-openwrt-buildroot.sh --help for options (custom OpenWrt version, config file, etc.).
If you want to serve pre-built packages to your routers without compiling on the router itself, you can create a signed opkg feed hosted on any HTTP server on your LAN (NAS, Raspberry Pi, etc.).
Follow the build environment setup above, then compile all packages:
cd ../openwrt
make package/ha-cluster/compile
make package/owsync/compile
make package/lease-sync/compile
make package/dnsmasq-ha/compile
make package/luci-app-ha-cluster/compileBuilt .ipk files are in bin/packages/<arch>/ha_feed/ (e.g., bin/packages/aarch64_cortex-a53/ha_feed/).
# usign is built as part of the OpenWrt toolchain
USIGN=staging_dir/host/bin/usign
# Generate a keypair (do this once, keep the private key safe)
$USIGN -G -p keys/ha_feed.pub -s keys/ha_feed.secThe OpenWrt buildroot has a built-in target that generates the package index (Packages, Packages.gz, Packages.sig, index.json) and signs it. Pass BUILD_KEY to use your feed signing key instead of the default build key:
make package/index BUILD_KEY=/path/to/keys/ha_feed.secCopy the .ipk files and index files to any directory served by an HTTP server:
FEED_DIR=bin/packages/<arch>/ha_feed
scp $FEED_DIR/*.ipk $FEED_DIR/Packages $FEED_DIR/Packages.gz $FEED_DIR/Packages.sig $FEED_DIR/index.json myserver:/var/www/openwrt/ha_feed/On each router:
# Add the feed
echo "src/gz ha_feed http://myserver/openwrt/ha_feed" > /etc/opkg/customfeeds.conf
# Install the signing public key
# The key fingerprint is the filename under /etc/opkg/keys/
FINGERPRINT=$(usign -F -p /path/to/ha_feed.pub)
cp /path/to/ha_feed.pub /etc/opkg/keys/$FINGERPRINT
# Update and install
opkg update
opkg install ha-cluster luci-app-ha-clusterAfter rebuilding packages, repeat steps 3 and 4 to regenerate the index and redeploy. For example, to rebuild only ha-cluster and luci-app-ha-cluster:
make package/ha-cluster/compile V=s
make package/luci-app-ha-cluster/compile V=s
make package/index BUILD_KEY=/path/to/keys/ha_feed.sec
scp bin/packages/<arch>/ha_feed/* myserver:/var/www/openwrt/ha_feed/On routers, run opkg update to pick up the new versions.
Note: dnsmasq-ha replaces the stock dnsmasq package. On first install, you may need to remove dnsmasq manually before installing dnsmasq-ha:
# Save dnsmasq config, remove stock, install HA version
cp /etc/config/dhcp /tmp/dhcp.bak
opkg remove dnsmasq
opkg install dnsmasq-ha
cp /tmp/dhcp.bak /etc/config/dhcp
/etc/init.d/dnsmasq restartPierre Gaufillet pierre.gaufillet@bergamote.eu