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 wireguard

Step 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 publickey

Note 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 = 25

10.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 = 25

PersistentKeepalive = 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/udp

Step 6 — Bring the tunnel up

On both servers:

wg-quick up wg0
# Enable on boot
systemctl enable wg-quick@wg0

Step 7 — Test connectivity

From Server A, ping Server B's tunnel IP:

ping 10.10.0.2

And check the tunnel status:

wg show

You 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/24

And enable IP forwarding on Server B:

echo "net.ipv4.ip_forward=1" >> /etc/sysctl.conf
sysctl -p

Add 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 MASQUERADE

Common 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.