← Start of tutorial

WIUNIX ACADEMY

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

Page 2 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
03

LET’S BUILD

Retain the forwarding baseline

On A/B after fresh-host checks. Existing files cause failure, not overwrite. Forwarding changes can reset IPv4 host/router defaults: compare sysctls before/after and account for tuning before production. This recipe assumes fresh stock gateways.

Bash
sudo bash -eu <<'BASELINE'
umask 077
install -d -m 0700 /etc/wireguard
for p in /etc/wireguard/private.key /etc/wireguard/wg0.conf /etc/wireguard/forwarding.before /etc/sysctl.d/70-academy-wireguard.conf /etc/systemd/system/academy-wg-filter.service; do
  if test -e "$p"; then echo 'Existing tutorial state: STOP' >&2; exit 1; fi
done
rules=$(nft list ruleset)
if test -n "$rules"; then echo 'Existing ruleset: STOP' >&2; exit 1; fi
sysctl -n net.ipv4.ip_forward > /etc/wireguard/forwarding.before
sysctl -a 2>/dev/null > /etc/wireguard/sysctl.before
BASELINE
How do I know it worked?

Original forwarding value and sysctl inventory are retained in a root-only directory.

04

LET’S BUILD

Generate keys locally; share only public keys

Run separately on A/B. Disable shell tracing and terminal recording. Private keys never enter command arguments, logs, Git or tickets. This design uses unique key pairs without an optional preshared key. If your threat model requires a PSK, manage it as a separate protected shared secret. Verify exchanged public keys through an authenticated channel.

Bash
sudo bash -eu <<'KEYS'
umask 077
test ! -e /etc/wireguard/private.key
test ! -e /etc/wireguard/public.key
wg genkey > /etc/wireguard/private.key
wg pubkey < /etc/wireguard/private.key > /etc/wireguard/public.key
chmod 0600 /etc/wireguard/private.key /etc/wireguard/public.key
KEYS
sudo cat /etc/wireguard/public.key
How do I know it worked?

A/B have different keys; only the public key is printed.

05

LET’S BUILD

Create each site’s protected template

On each gateway put only its next FILE CONTENT in the editor. Replace peer PUBLIC keys and public endpoints; leave the local private-key marker until the protected insertion step. Never copy the other gateway’s private key.

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

The protected file contains this site’s template and opposite public identity.

A: wg0.conf contents

FILE CONTENT on A only: peer B’s identity and prefixes. No SaveConfig, DNS or default-route setting.

INI · /etc/wireguard/wg0.conf
[Interface]
Address = 10.255.255.1/30
ListenPort = 51820
PrivateKey = REPLACE_LOCAL_PRIVATE_KEY

[Peer]
PublicKey = REPLACE_B_PUBLIC_KEY
Endpoint = 203.0.113.20:51820
AllowedIPs = 10.255.255.2/32, 10.20.20.0/24

B: wg0.conf contents

FILE CONTENT on B only: peer A’s identity and prefixes. The baseline assumes stable reachable public endpoints on both sides.

INI · /etc/wireguard/wg0.conf
[Interface]
Address = 10.255.255.2/30
ListenPort = 51820
PrivateKey = REPLACE_LOCAL_PRIVATE_KEY

[Peer]
PublicKey = REPLACE_A_PUBLIC_KEY
Endpoint = 198.51.100.10:51820
AllowedIPs = 10.255.255.1/32, 10.10.10.0/24
06

LET’S BUILD

Insert the local private key without displaying it

On A/B after editing public placeholders. The script refuses missing/duplicate markers and remaining peer placeholders. Do not share showconf, dump, completed config output or editor screenshots: they can contain secrets.

Bash
sudo python3 - <<'CONFIG'
import os
from pathlib import Path
p = Path('/etc/wireguard/wg0.conf')
s = p.read_text()
marker = 'REPLACE_LOCAL_PRIVATE_KEY'
if s.count(marker) != 1 or 'REPLACE_' in s.replace(marker, ''):
    raise SystemExit('Unedited or duplicate placeholders: STOP')
k = Path('/etc/wireguard/private.key').read_text().strip()
if len(k) != 44:
    raise SystemExit('Invalid local key length: STOP')
p.write_text(s.replace(marker, k))
os.chmod(p, 0o600)
print('Local key inserted; configuration not displayed')
CONFIG
How do I know it worked?

Only a completion message appears; the private key stays in protected files.

Explore all tutorials ↗