← Start of tutorial

WIUNIX ACADEMY

Set up a routed WireGuard site-to-site VPN on Ubuntu and Debian

Page 3 of 8. Review the earlier steps and prerequisites before continuing.

YOUR SYSTEM. YOUR STEPS.

Where are you working?

Choose your OS and version. The prerequisites, commands and troubleshooting follow your environment.

Ubuntu 26.10Reviewed against official documentation ·

USE YOUR OWN VALUES

Configure this tutorial

Set the example values for your environment; related explanations and copy panels update together. Never enter passwords, private keys or tokens. Values stay in this tab and are not sent to the server or share links. Check actual DNS, certificates and network policy before running commands.

Different IP addresses, domains or ports? Open the settings below, change the values and select “Apply to tutorial”, then copy the updated commands.

Set values for my environment24 editable values

NAT endpoints and keepalive

If B is behind NAT, A must be reachable: forward UDP 51820 on A’s edge when necessary. B initiates toward A; add the next optional line under B’s existing Peer only when idle mappings must stay open. On A remove B’s Endpoint if B’s public address/port is unstable; it is learned after authentication. A’s UDP input source rule must match B’s real egress IP, or a deliberately approved wider source set for roaming peers. B needs permitted output and stateful replies. Two unforwardable CGNAT sites need a reachable hub/provider address; keepalive cannot create one. Public-edge NAT is distinct from inner-LAN masquerading, which this guide avoids.

Optional B Peer addition

FILE CONTENT, only when NAT idle reachability needs it; omit in the public/public baseline.

INI · /etc/wireguard/wg0.conf
PersistentKeepalive = 25
07

LET’S BUILD

Enable IPv4 forwarding on A/B

The new dedicated file changes only this setting; it does not grant firewall permission. Do not overwrite an existing owner’s sysctl file.

Bash
sudo bash -eu <<'FORWARD'
test ! -e /etc/sysctl.d/70-academy-wireguard.conf
printf 'net.ipv4.ip_forward = 1\n' > /etc/sysctl.d/70-academy-wireguard.conf
sysctl -p /etc/sysctl.d/70-academy-wireguard.conf
FORWARD
How do I know it worked?

IPv4 forwarding is 1 on both gateways.

08

LET’S BUILD

Prepare the site-specific filter

Use the following A or B FILE CONTENT on its matching gateway. Interface names and peer addresses follow the configuration fields. Management source/SSH22 must match the independent IPv4 administration path before activation. The dedicated inet table defaults input/forward to drop without flushing rules. Do not enable through IPv6-only SSH; ICMPv6 here supports underlay control, not an IPv6 VPN.

Bash
sudo install -o root -g root -m 0600 /dev/null /etc/wireguard/academy-filter.nft
sudo nano /etc/wireguard/academy-filter.nft
How do I know it worked?

Rules preserve actual management and restrict site traffic.

A filter contents; B substitutions

FILE CONTENT on A only. Hosts must separately allow approved ICMP/TCP 8080 from the opposite test host in their own firewall. No arbitrary production ports.

nftables · /etc/wireguard/academy-filter.nft
define wan = "ens18"
define lan = "ens19"
define local_net = 10.10.10.0/24
define remote_net = 10.20.20.0/24
define local_host = 10.10.10.100
define remote_host = 10.20.20.100
define peer_wan = 203.0.113.20
define management = 192.0.2.0/24

table inet academy_wg {
  chain input {
    type filter hook input priority 0; policy drop;
    iifname "lo" accept
    ct state invalid drop
    ct state established,related accept
    ip saddr $management tcp dport 22 counter accept
    iifname $wan ip saddr $peer_wan udp dport 51820 counter accept
    ip protocol icmp counter accept
    meta l4proto ipv6-icmp accept
  }
  chain forward {
    type filter hook forward priority 0; policy drop;
    ct state invalid drop
    ct state established,related counter accept
    iifname $lan oifname "wg0" ip saddr $local_net ip daddr $remote_net ip protocol icmp counter accept
    iifname "wg0" oifname $lan ip saddr $remote_net ip daddr $local_net ip protocol icmp counter accept
    iifname $lan oifname "wg0" ip saddr $local_host ip daddr $remote_host tcp dport 8080 counter accept
    iifname "wg0" oifname $lan ip saddr $remote_host ip daddr $local_host tcp dport 8080 counter accept
  }
}

Site B firewall file contents

FILE CONTENT on B. Values below follow the B gateway fields directly; do not copy A’s peer or interface values.

nftables · /etc/wireguard/academy-filter.nft
define wan = "ens18"
define lan = "ens19"
define local_net = 10.20.20.0/24
define remote_net = 10.10.10.0/24
define local_host = 10.20.20.100
define remote_host = 10.10.10.100
define peer_wan = 198.51.100.10
define management = 192.0.2.0/24

table inet academy_wg {
  chain input {
    type filter hook input priority 0; policy drop;
    iifname "lo" accept
    ct state invalid drop
    ct state established,related accept
    ip saddr $management tcp dport 22 counter accept
    iifname $wan ip saddr $peer_wan udp dport 51820 counter accept
    ip protocol icmp counter accept
    meta l4proto ipv6-icmp accept
  }
  chain forward {
    type filter hook forward priority 0; policy drop;
    ct state invalid drop
    ct state established,related counter accept
    iifname $lan oifname "wg0" ip saddr $local_net ip daddr $remote_net ip protocol icmp counter accept
    iifname "wg0" oifname $lan ip saddr $remote_net ip daddr $local_net ip protocol icmp counter accept
    iifname $lan oifname "wg0" ip saddr $local_host ip daddr $remote_host tcp dport 8080 counter accept
    iifname "wg0" oifname $lan ip saddr $remote_host ip daddr $local_host tcp dport 8080 counter accept
  }
}
Explore all tutorials ↗