> ## Content Index
> Fetch the complete content index at: https://blog.sudowl.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# WireGuard VPN on Ubuntu — the Setup I Actually Use
- URL: https://blog.sudowl.com/wireguard-vpn-server-ubuntu/
- Published: 2026-04-21T19:30:00.000Z
- Updated: 2026-10-11T08:47:37.000Z
- Description: Minimal WireGuard setup on Ubuntu. Server config, one client, UFW rule, QR code for the phone. The version I run on my home-lab jumpbox.
- Author: Lukas
- Tags: Security, Ubuntu, Linux

I spent a weekend about a year ago setting this up on a Proxmox VM on node1, now it's the thing I SSH through every day. Writing it down before I forget the ten-minute version.

**TL;DR**

```bash
sudo apt install wireguard qrencode
wg genkey | sudo tee /etc/wireguard/srv.key | wg pubkey | sudo tee /etc/wireguard/srv.pub
# drop wg0.conf below, then:
sudo systemctl enable --now wg-quick@wg0
sudo ufw allow 51820/udp

```

That's it on the server side. Below is the config and what actually burned me the first time.

## The server config

```ini
# /etc/wireguard/wg0.conf
[Interface]
PrivateKey = <srv.key contents>
Address = 10.8.0.1/24
ListenPort = 51820

PostUp   = iptables -t nat -A POSTROUTING -s 10.8.0.0/24 -o ens18 -j MASQUERADE
PostDown = iptables -t nat -D POSTROUTING -s 10.8.0.0/24 -o ens18 -j MASQUERADE

[Peer]
# laptop
PublicKey = <paste later>
AllowedIPs = 10.8.0.2/32

[Peer]
# phone
PublicKey = <paste later>
AllowedIPs = 10.8.0.3/32

```

Two things I got wrong on the first attempt:

- `-o ens18` — that's the external interface on my Proxmox VM. On bare Ubuntu it's often `eth0`, on cloud VPS usually `ens3` or `enp1s0`. `ip -br link` tells you. Get this wrong and clients connect to the tunnel fine but can't reach the internet, which is confusing because the tunnel *works*
- `10.8.0.0/24` — pick a range you don't use anywhere else. I already had `10.0.0.0/24` on the LAN and `10.10.0.0/24` on another VPN, so 10.8 was free. Collision here eats hours

You also need IP forwarding on. If you don't have it already:

```bash
echo 'net.ipv4.ip_forward = 1' | sudo tee /etc/sysctl.d/99-wg.conf
sudo sysctl --system

```

## Client config

On the client machine, gen a keypair and write this:

```ini
[Interface]
PrivateKey = <client private key>
Address = 10.8.0.2/32
DNS = 1.1.1.1

[Peer]
PublicKey = <server public key>
Endpoint = node1.mydomain.tld:51820
AllowedIPs = 0.0.0.0/0, ::/0
PersistentKeepalive = 25

```

`AllowedIPs = 0.0.0.0/0` pushes everything through the tunnel. For split-tunnel (only reach the internal network, normal traffic stays local), use `10.8.0.0/24, 192.168.0.0/24` or whatever your internal range is.

Paste the client's **public** key into the `[Peer]` block in the server's `wg0.conf`, reload:

```bash
sudo wg syncconf wg0 <(wg-quick strip wg0)

```

`syncconf` re-reads without nuking existing connections. `systemctl restart` kills them. Use `syncconf`.

## Phone

This is where WireGuard actually beats everything else. Generate the phone config on the server, cat it into qrencode, scan with the WireGuard Android/iOS app:

```bash
qrencode -t ansiutf8 < /etc/wireguard/phone.conf

```

Five seconds. No typing a 44-char base64 key on a phone keyboard.

After the scan, `shred -u phone.conf` so it's not sitting around.

## Firewall + SSH

After the VPN is up, move SSH behind it and stop exposing port 22:

```bash
# /etc/ssh/sshd_config
ListenAddress 10.8.0.1

```

```bash
sudo ufw deny 22/tcp
sudo ufw allow from 10.8.0.0/24 to any port 22
sudo systemctl reload ssh

```

Check from a second SSH session *first* — don't close the original until you've tested the new one. I've locked myself out this way. Twice.

If you haven't hardened SSH beyond this, the full checklist is at [SSH hardening](/ssh-hardening-ubuntu-checklist/), and [fail2ban](/fail2ban-setup-ssh-nginx/) is worth the five minutes even behind a VPN.

## Two gotchas I burned time on

**Handshake never happens.** `sudo wg show` shows "latest handshake: (never)". Almost always UDP 51820 not reaching the server. My first time this was the hoster's inbound ACL, not my UFW. If `ss -unlp | grep 51820` on the server shows wireguard listening, the port is open locally — it's upstream.

**Tunnel up, but no internet through it.** This is IP forwarding off, or the MASQUERADE rule has the wrong outbound interface. Check `sysctl net.ipv4.ip_forward` is 1, then `sudo iptables -t nat -L POSTROUTING -v` to see if packets are hitting the MASQUERADE rule. Zero packets = wrong interface name.

## Why this works for me

node1 runs Proxmox, one VM runs nginx + a bunch of other stuff, and this WireGuard instance lives on a small dedicated VM. My phone, laptop, and anything else I need mobile access to all land on `10.8.0.0/24`, and from there I can reach every other VM on the internal bridge. No exposing SSH, no port forwarding per service.

If you're looking for a reason to care: WireGuard is the first VPN I've run that I don't actively hate.

---

### Related posts

- [SSH Hardening Checklist for Ubuntu Servers](https://blog.diengdoh.com/ssh-hardening-ubuntu-checklist/?ref=blog.sudowl.com)
- [Restic Backups to S3 / MinIO — Encrypted, Deduplicated, Restore-Tested](https://blog.diengdoh.com/restic-backup-s3-minio-encrypted/?ref=blog.sudowl.com)
- [Netdata on Ubuntu — Live Server Metrics in 5 Minutes](https://blog.diengdoh.com/netdata-monitoring-ubuntu/?ref=blog.sudowl.com)