WireGuard is the fastest, simplest way to build an encrypted tunnel between two servers — a site-to-site VPN for private traffic between your origin and edge nodes, database replication, or a management network. This guide connects two Hostfory servers (or a Hostfory box and your office) with a persistent WireGuard tunnel.
Prerequisites
- Two Linux servers with root access and public IPs (call them Server A and Server B)
- Ubuntu 22.04 (WireGuard is in-kernel since 5.6)
- One UDP port open between them (default 51820)
Step 1 — Install WireGuard on both servers
apt update
apt install -y wireguardStep 2 — Generate keys on both servers
On each server, generate a private/public keypair:
cd /etc/wireguard
umask 077
wg genkey | tee privatekey | wg pubkey > publickey
cat privatekey publickeyNote each server's public key — you'll cross-reference them. Keep private keys secret.
Step 3 — Configure Server A
Create /etc/wireguard/wg0.conf on Server A:
[Interface]
Address = 10.10.0.1/24
ListenPort = 51820
PrivateKey = <SERVER_A_PRIVATE_KEY>
[Peer]
# Server B
PublicKey = <SERVER_B_PUBLIC_KEY>
Endpoint = <SERVER_B_PUBLIC_IP>:51820
AllowedIPs = 10.10.0.2/32
PersistentKeepalive = 2510.10.0.1/24 is the private tunnel IP for Server A. AllowedIPs says "route traffic for 10.10.0.2 through this peer."
Step 4 — Configure Server B
Create /etc/wireguard/wg0.conf on Server B (mirror image):
[Interface]
Address = 10.10.0.2/24
ListenPort = 51820
PrivateKey = <SERVER_B_PRIVATE_KEY>
[Peer]
# Server A
PublicKey = <SERVER_A_PUBLIC_KEY>
Endpoint = <SERVER_A_PUBLIC_IP>:51820
AllowedIPs = 10.10.0.1/32
PersistentKeepalive = 25PersistentKeepalive = 25 keeps the tunnel alive through NAT/firewalls by sending a keepalive every 25 seconds — important if either side is behind NAT.
Step 5 — Open the firewall
On both servers, allow the WireGuard UDP port:
ufw allow 51820/udpStep 6 — Bring the tunnel up
On both servers:
wg-quick up wg0
# Enable on boot
systemctl enable wg-quick@wg0Step 7 — Test connectivity
From Server A, ping Server B's tunnel IP:
ping 10.10.0.2And check the tunnel status:
wg showYou want to see a recent handshake (latest handshake: X seconds ago) and traffic counters climbing. If handshake never happens, the UDP port isn't reaching the peer — check firewalls on both ends and any upstream network ACLs.
Step 8 — Route a whole subnet (optional)
To route all traffic for a subnet (e.g. Server B's private network 192.168.50.0/24) through the tunnel, expand AllowedIPs on Server A's peer block:
AllowedIPs = 10.10.0.2/32, 192.168.50.0/24And enable IP forwarding on Server B:
echo "net.ipv4.ip_forward=1" >> /etc/sysctl.conf
sysctl -pAdd a NAT rule on Server B so return traffic finds its way back:
iptables -t nat -A POSTROUTING -s 10.10.0.0/24 -o eth0 -j MASQUERADECommon use-cases on Hostfory infrastructure
- Origin ↔ edge private link: keep your streaming origin's real IP hidden; edges pull over the tunnel, only the edge IPs are public.
- Database replication: run Postgres/MySQL replication over the encrypted tunnel instead of exposing the DB port publicly.
- Management network: SSH, monitoring (Prometheus scraping), and backups over 10.10.0.0/24 — never over the public interface.
- Multi-PoP mesh: connect all four of our PoPs (Amsterdam, Kyiv, Warsaw, Miami) in a WireGuard mesh for private inter-PoP traffic.
Troubleshooting
Handshake but no ping: AllowedIPs mismatch. Each peer must list the other's tunnel IP. A common mistake is 0.0.0.0/0 on both sides, which creates a routing loop.
Tunnel drops after a while: add or lower PersistentKeepalive. NAT tables time out UDP flows; keepalive prevents that.
Works until reboot: you forgot systemctl enable wg-quick@wg0.
Where to go from here
For a hardened management setup, restrict SSH to only listen on the tunnel IP (ListenAddress 10.10.0.1 in sshd_config) so the public interface never exposes SSH at all. Combine with Fail2ban for defense in depth.