WireGuard Remote Access: Ubuntu ↔︎ Windows

A concise guide for setting up WireGuard VPN to remotely access an Ubuntu machine from Windows, with SSH locked to the tunnel for security.

Overview


Part 1: Ubuntu Server Setup

1.1 Install WireGuard

sudo apt update
sudo apt install wireguard -y

The kernel module is built into Ubuntu's kernel (since 5.6) — no extra packages needed.

1.2 Generate server keys

cd /etc/wireguard
sudo sh -c 'umask 077; wg genkey | tee server_private.key | wg pubkey > server_public.key'

View them:

sudo cat /etc/wireguard/server_private.key
sudo cat /etc/wireguard/server_public.key

1.3 Create server config

Create /etc/wireguard/wg0.conf:

[Interface]
Address = 10.10.10.1/24
ListenPort = 51820
PrivateKey = <paste server_private.key contents>

[Peer]
PublicKey = <Windows client public key — fill in after Part 2>
AllowedIPs = 10.10.10.2/32

Set permissions:

sudo chmod 600 /etc/wireguard/wg0.conf

1.4 Configure firewall (UFW)

# Allow WireGuard port
sudo ufw allow 51820/udp

# Allow all traffic on the tunnel interface (trusted by virtue of crypto auth)
sudo ufw allow in on wg0

# Enable UFW if not already active
sudo ufw enable

1.5 Start and enable the tunnel

sudo systemctl enable --now wg-quick@wg0

1.6 Router port forward

On your home router, forward UDP 51820 to the Ubuntu box's LAN IP.

Find the Ubuntu LAN IP:

ip -4 addr show | grep inet

Find your public IP (what to put in Windows Endpoint):

curl -4 ifconfig.me

⚠️ CGNAT check: if curl -4 ifconfig.me doesn't match your router's WAN IP, you're behind CGNAT and port forwarding won't work. Use Tailscale instead.


Part 2: Windows Client Setup

2.1 Install WireGuard

Download from wireguard.com/install and install.

2.2 Create empty tunnel

Open WireGuard → Add Tunnel → Add empty tunnel...

A keypair is auto-generated. Copy the public key shown at the top — you'll paste it into the Ubuntu config.

2.3 Fill in the config

Replace the dialog contents with:

[Interface]
PrivateKey = <leave the auto-generated one>
Address = 10.10.10.2/32
DNS = 1.1.1.1

[Peer]
PublicKey = <Ubuntu server's public key>
Endpoint = <your-public-ip-or-ddns>:51820
AllowedIPs = 10.10.10.0/24
PersistentKeepalive = 25

Name the tunnel (e.g. ubuntu-home) and save.

2.4 Paste Windows public key into Ubuntu config

Back on Ubuntu:

sudo nano /etc/wireguard/wg0.conf

Replace the <Windows client public key> placeholder with the actual key from Windows, then restart:

sudo systemctl restart wg-quick@wg0

2.5 Activate on Windows

In the WireGuard GUI, click Activate. Status should flip to "Active" within seconds.


Part 3: Verification & Sanity Checks

3.1 Check WireGuard is running (Ubuntu)

sudo systemctl status wg-quick@wg0
sudo wg show
ip -brief addr | grep wg0

Expected: service active, wg0 interface with 10.10.10.1/24, listening port 51820.

3.2 Verify keys match on both sides

Server public key (Ubuntu):

sudo wg show wg0 public-key

This must equal the [Peer] PublicKey in your Windows config.

Check peer public key Ubuntu expects:

sudo wg show wg0 | grep peer

This must equal the Public key shown in the Windows WireGuard GUI.

Validate key lengths (should all be 44 chars ending in =):

sudo grep -E "PrivateKey|PublicKey" /etc/wireguard/wg0.conf | awk '{print $3, "length:", length($3)}'

3.3 Sanity-check config file

sudo cat /etc/wireguard/wg0.conf
sudo ls -la /etc/wireguard/

Config should have both [Interface] and [Peer] blocks with no placeholder text. Permissions should be -rw------- (600), owned by root.

Validate config parses correctly:

sudo wg-quick strip wg0

Prints clean config if valid, errors if not.

3.4 Verify firewall state

sudo ufw status verbose
sudo ufw status numbered

Should show:

3.5 Confirm port is listening

sudo ss -ulnp | grep 51820

Should show something bound to *:51820.

3.6 Test handshake live

On Ubuntu, run tcpdump to watch packets. First identify your WAN-facing interface:

ip route | grep default

Note the dev <name> — that's your WAN interface. Then:

sudo tcpdump -i any -n udp port 51820

(-i any captures on all interfaces, easiest for troubleshooting.)

Then click Activate on Windows (from outside your LAN — use phone hotspot). You should see both incoming and outgoing packets within a second.

Interpreting tcpdump output:

3.7 Confirm handshake completed

After activating Windows tunnel:

sudo wg show

Look for:

3.8 Test connectivity

From Windows PowerShell with tunnel active:

ping 10.10.10.1
ssh <user>@10.10.10.1

Once SSH works through the tunnel, remove it from public exposure.

4.1 Remove SSH from UFW

# Remove any existing SSH allow rules (check current rules first)
sudo ufw status numbered
sudo ufw delete allow ssh
sudo ufw delete allow 22/tcp

SSH remains reachable through the tunnel because wg0 is allowed at the interface level.

4.2 Verify SSH still works via tunnel

From Windows (tunnel active):

ssh <user>@10.10.10.1

4.3 Verify SSH blocked from LAN

From any other LAN device:

ssh <user>@<ubuntu-lan-ip>

Should time out or be refused.

4.4 Optional: Key-only SSH auth

Generate an Ed25519 key on Windows:

ssh-keygen -t ed25519
ssh-copy-id <user>@10.10.10.1

Then disable password auth on Ubuntu — edit /etc/ssh/sshd_config:

PasswordAuthentication no
PermitRootLogin no

Reload:

sudo systemctl reload ssh

Note: on Ubuntu the service is called ssh, not sshd (though sshd is usually aliased).


Part 5: Troubleshooting Quick Reference

No handshake (bytes sent but not received)

  1. Check tcpdump on Ubuntu — are packets arriving?
  2. If no: router port forward, stale endpoint IP, or CGNAT
  3. If yes but no response: firewall or key mismatch

Endpoint IP changed (dynamic home IP)

curl -4 ifconfig.me

Update the Windows config's Endpoint = line with the new value.

Tunnel shows active but ping fails

Check wg0 is allowed in UFW:

sudo ufw status verbose | grep wg0

If missing:

sudo ufw allow in on wg0

Service fails to start

sudo journalctl -u wg-quick@wg0 -n 50 --no-pager

Common causes:

UFW rules not persisting

UFW rules persist automatically. If they seem to disappear, check UFW is enabled:

sudo ufw status

If it says "inactive":

sudo ufw enable

Verify after reboot

sudo systemctl is-enabled wg-quick@wg0   # should say: enabled
sudo wg show
ip -brief addr | grep wg0
sudo ufw status

Final Expected State

Ubuntu UFW

Status: active

To                         Action      From
--                         ------      ----
51820/udp                  ALLOW IN    Anywhere
Anywhere on wg0            ALLOW IN    Anywhere
51820/udp (v6)             ALLOW IN    Anywhere (v6)
Anywhere (v6) on wg0       ALLOW IN    Anywhere (v6)

What you can do now


Data Usage Reference (over tunnel)

Activity Data/hour
SSH (terminal) 1-10 MB
X11 forwarding (single GUI app) 20-100 MB
RDP, tuned for low bandwidth 100-200 MB
RDP, defaults 200-500 MB
RDP with video 1-5 GB (avoid)

Use SSH for most things, RDP only when GUI is necessary.