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:
sudo apt update
sudo apt install -y wireguard qrencodeThe kernel module ships with Ubuntu 24.04's kernel, so there is nothing to compile. Check the tools are there:
wg --versionCreate 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:
sudo -i
cd /etc/wireguard
umask 077
wg genkey | tee server.key | wg pubkey > server.pub
cat server.pubKeep 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:
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:
[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 MASQUERADEWhat each line does:
Addressgives the server its address inside the tunnel.10.8.0.0/24andfd08::/64are private ranges: pick others if they clash with a network you connect from (many home routers use192.168.1.0/24, which is why this tutorial avoids it).ListenPortis the UDP port WireGuard listens on. 51820 is the usual choice; any free UDP port works.PostUpruns 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.PostDownremoves 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:
cat > /etc/sysctl.d/99-wireguard.conf <<'EOF'
net.ipv4.ip_forward = 1
net.ipv6.conf.all.forwarding = 1
EOF
sysctl --systemA 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:
ufw allow 51820/udp
ufw route allow in on wg0 out on eth0If the server has a panel firewall with a deny default, add this rule on its Firewall tab:
allow udp 51820Keep 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:
wg genkey | tee laptop.key | wg pubkey > laptop.pubAdd the laptop to the end of /etc/wireguard/wg0.conf, with the contents of laptop.pub:
[Peer]
# laptop
PublicKey = LAPTOP_PUBLIC_KEY
AllowedIPs = 10.8.0.2/32, fd08::2/128On 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:
[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 = 25On 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:
systemctl enable --now wg-quick@wg0
wg showwg 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):
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.confand activate it. - Linux: install WireGuard, move the file to
/etc/wireguard/and start it withsudo 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:
cd /etc/wireguard
wg genkey | tee phone.key | wg pubkey > phone.pubAdd 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:
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):
qrencode -t ansiutf8 < /etc/wireguard/phone.confClear 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:
wg show wg0 latest-handshakesEach 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:
ping -c 3 10.8.0.1
curl -4 https://icanhazip.comThe 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.