# 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.

Source: https://vpsnine.com/tutorials/wireguard-vpn-server-ubuntu · Updated: 2026-10-21

Difficulty: intermediate · Time: about 30 minutes · Tested on: Ubuntu 24.04 LTS

Prerequisites:

- 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](https://vpsnine.com/help/server-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.
