How to set up a WireGuard VPN server on Ubuntu 24.04

Install WireGuard on Ubuntu 24.04, write the server config, route clients' traffic through it and connect a laptop and a phone.

Tested on Ubuntu 24.04 LTS

Before you start

  • A server running Ubuntu 24.04 with a public IPv4 address
  • Root or a user with sudo
  • An SSH session to the server
  • A laptop or phone with the WireGuard app

To set up a WireGuard VPN server on Ubuntu 24.04, install the wireguard package, create a key pair, write /etc/wireguard/wg0.conf with a private tunnel network and NAT rules, turn on IP forwarding, open UDP port 51820, and add one [Peer] per device. This tutorial does each step with the exact commands and files, then connects a laptop and a phone and checks that traffic really leaves through the server.

WireGuard runs in the Linux kernel, has a small configuration and only answers packets signed with a key it knows, so it doesn't show up to port scanners. A VPN like this changes where your traffic leaves the internet (your server's address instead of the café's Wi-Fi); it doesn't make you anonymous.

The examples use these addresses. Replace them with yours:

What In this tutorial
The server's public IPv4 address 203.0.113.10
The tunnel network (IPv4) 10.8.0.0/24, server 10.8.0.1
The tunnel network (IPv6) fd08::/64, server fd08::1
The laptop 10.8.0.2 and fd08::2
The phone 10.8.0.3 and fd08::3
The server's network interface eth0

Install WireGuard

Update the package lists and install WireGuard's tools, plus qrencode for the phone's QR code later:

bash
sudo apt update
sudo apt install -y wireguard qrencode

The kernel module ships with Ubuntu 24.04's kernel, so there is nothing to compile. Check the tools are there:

bash
wg --version

Create the server's keys

Every WireGuard peer has a private key it keeps and a public key it hands out. Create the server's pair in /etc/wireguard, readable by root only:

bash
sudo -i
cd /etc/wireguard
umask 077
wg genkey | tee server.key | wg pubkey > server.pub
cat server.pub

Keep this root shell open for the next steps. server.key never leaves the server; server.pub goes into every device's config.

Find the network interface

The NAT rules need the name of the interface that faces the internet. Ask for the one that carries the default route:

bash
ip -o -4 route show to default | awk '{print $5}'

On a VPSNine server it prints eth0. If yours prints something else (ens3, enp1s0), use that name wherever this tutorial says eth0.

Write the server config

Create /etc/wireguard/wg0.conf. Paste the contents of server.key after PrivateKey:

ini
[Interface]
Address = 10.8.0.1/24, fd08::1/64
ListenPort = 51820
PrivateKey = SERVER_PRIVATE_KEY

# Route the clients' traffic out of eth0, rewriting their tunnel address to the server's.
PostUp = iptables -A FORWARD -i wg0 -j ACCEPT; iptables -A FORWARD -o wg0 -m state --state RELATED,ESTABLISHED -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostUp = ip6tables -A FORWARD -i wg0 -j ACCEPT; ip6tables -A FORWARD -o wg0 -m state --state RELATED,ESTABLISHED -j ACCEPT; ip6tables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i wg0 -j ACCEPT; iptables -D FORWARD -o wg0 -m state --state RELATED,ESTABLISHED -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
PostDown = ip6tables -D FORWARD -i wg0 -j ACCEPT; ip6tables -D FORWARD -o wg0 -m state --state RELATED,ESTABLISHED -j ACCEPT; ip6tables -t nat -D POSTROUTING -o eth0 -j MASQUERADE

What each line does:

  • Address gives the server its address inside the tunnel. 10.8.0.0/24 and fd08::/64 are private ranges: pick others if they clash with a network you connect from (many home routers use 192.168.1.0/24, which is why this tutorial avoids it).
  • ListenPort is the UDP port WireGuard listens on. 51820 is the usual choice; any free UDP port works.
  • PostUp runs when the tunnel comes up: it lets traffic from the tunnel be forwarded, lets replies back in, and masquerades (NATs) it behind the server's own address. PostDown removes the same rules when the tunnel goes down.

The peers (your devices) are added at the end of this file in a later step.

Turn on IP forwarding

By default Linux doesn't pass packets from one interface to another. Turn forwarding on for IPv4 and IPv6, now and at every boot:

bash
cat > /etc/sysctl.d/99-wireguard.conf <<'EOF'
net.ipv4.ip_forward = 1
net.ipv6.conf.all.forwarding = 1
EOF
sysctl --system

A VPSNine server's IPv6 address and route are set statically by cloud-init, so turning on IPv6 forwarding doesn't drop them. On a host that learns its IPv6 route from router advertisements instead, also set net.ipv6.conf.eth0.accept_ra = 2, or the server stops listening to them.

Open UDP port 51820

Two firewalls may stand in the way: the one inside the server (ufw) and, on VPSNine, the panel's firewall in front of it.

If ufw is active (ufw status says Status: active), allow WireGuard's port and let ufw route the tunnel's traffic:

bash
ufw allow 51820/udp
ufw route allow in on wg0 out on eth0

If the server has a panel firewall with a deny default, add this rule on its Firewall tab:

text
allow udp 51820

Keep your SSH rule in both places, or you lock yourself out of the server.

Add your first device

Each device gets its own key pair and its own tunnel address. You can make the keys on the device (the WireGuard apps have a button for it) or on the server. Making them on the server is quicker, and the private key then only has to travel once, in the config file you copy to the device.

Still in /etc/wireguard, make the laptop's keys:

bash
wg genkey | tee laptop.key | wg pubkey > laptop.pub

Add the laptop to the end of /etc/wireguard/wg0.conf, with the contents of laptop.pub:

ini
[Peer]
# laptop
PublicKey = LAPTOP_PUBLIC_KEY
AllowedIPs = 10.8.0.2/32, fd08::2/128

On the server, AllowedIPs says which tunnel addresses this peer may use; packets from any other source address are dropped. One address per device, so /32 and /128.

Now write the laptop's own config, laptop.conf. PrivateKey is the contents of laptop.key, PublicKey the contents of server.pub:

ini
[Interface]
PrivateKey = LAPTOP_PRIVATE_KEY
Address = 10.8.0.2/32, fd08::2/128

[Peer]
PublicKey = SERVER_PUBLIC_KEY
Endpoint = 203.0.113.10:51820
AllowedIPs = 0.0.0.0/0, ::/0
PersistentKeepalive = 25

On the device, AllowedIPs = 0.0.0.0/0, ::/0 means "send everything through the tunnel". To reach only the server and the tunnel network (and keep the rest of your traffic on the local connection), use AllowedIPs = 10.8.0.0/24, fd08::/64 instead. PersistentKeepalive sends a small packet every 25 seconds so a home router or mobile network doesn't forget the connection while it is idle.

The laptop keeps using its own DNS servers. If you want DNS lookups to go through the tunnel too, add a DNS = line under [Interface] with a resolver you trust.

Start the tunnel

Start WireGuard and have it come back after every reboot:

bash
systemctl enable --now wg-quick@wg0
wg show

wg show lists the wg0 interface, its port and the laptop's peer. It shows no handshake yet: that comes when the laptop connects. If wg-quick fails, journalctl -u wg-quick@wg0 says which line it didn't like.

Connect the laptop

Copy laptop.conf to the laptop over SSH (from the laptop):

bash
scp root@203.0.113.10:/etc/wireguard/laptop.conf .

Then:

  • Windows and macOS: open the WireGuard app, choose Import tunnel(s) from file, pick laptop.conf and activate it.
  • Linux: install WireGuard, move the file to /etc/wireguard/ and start it with sudo wg-quick up laptop (it takes the file's name).

Delete the copy of laptop.conf in your downloads once it is imported: it holds the laptop's private key.

Connect a phone with a QR code

Make the phone's keys and config the same way, with the next address:

bash
cd /etc/wireguard
wg genkey | tee phone.key | wg pubkey > phone.pub

Add its [Peer] to wg0.conf with AllowedIPs = 10.8.0.3/32, fd08::3/128, write phone.conf like laptop.conf with Address = 10.8.0.3/32, fd08::3/128, and apply the new peer without dropping the laptop's connection:

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

Show the config as a QR code in the terminal and scan it with the WireGuard app (Add a tunnel, then Create from QR code or Scan from QR code):

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

Clear the terminal afterwards (clear): the QR code holds the phone's private key.

Check that it works

With the tunnel active on the laptop, check the handshake on the server:

bash
wg show wg0 latest-handshakes

Each connected peer shows a recent Unix timestamp. Then, from the laptop, check that you can reach the server inside the tunnel and that the internet sees the server's address:

bash
ping -c 3 10.8.0.1
curl -4 https://icanhazip.com

The second command should print your server's address, 203.0.113.10 in this tutorial, not your home or office address.

Fix the common problems

No handshake at all. The packets don't reach the server. Check that the tunnel is up (wg show), that UDP 51820 is open in ufw and in the panel firewall, and that the Endpoint in the device's config has the right address and port. Some guest Wi-Fi networks block UDP; try from a phone's mobile data.

Handshake, but no internet. The server receives the packets but doesn't pass them on. Check forwarding (sysctl net.ipv4.ip_forward must print 1), that the interface name in PostUp matches yours, and that ufw has the route allow rule. iptables -t nat -L POSTROUTING -v shows whether the masquerade rule is there and counting packets.

Websites load by address but not by name. DNS isn't answering through the tunnel. Remove the DNS = line to use the device's own resolvers, or point it at a resolver you know answers.

One device works, another doesn't. Two peers with the same key or the same tunnel address knock each other off. Give every device its own key pair and address.

Add and remove devices later

To add a device, repeat the phone's steps with the next free address (10.8.0.4, fd08::4, and so on) and run wg syncconf wg0 <(wg-quick strip wg0).

To remove one, delete its [Peer] block from wg0.conf, run the same wg syncconf command, and delete its .key and .conf files from /etc/wireguard. Its key stops working at once.

WireGuard's tools update with the rest of the system (apt upgrade), and the kernel part with the kernel. Nothing else needs looking after.

Be first when orders open

Join the waitlist and we'll send you one email when VPSNine launches. Pick a plan if you already know which one you want, and we'll size the first servers around it.

  • One email, on launch day
  • Monthly or yearly, no lock-in
  • 14-day money-back guarantee