WireGuard is a modern VPN built into the Linux kernel. It uses a small set of well-reviewed cryptographic primitives, is configured with short text files, and is typically faster than OpenVPN or IPsec. In this tutorial you will turn an Ubuntu 24.04 server into a WireGuard VPN gateway, connect a laptop and a phone to it, and send all their internet traffic through the server.
Prerequisites
To follow this guide you need:
- A server running Ubuntu 24.04 LTS with a public IPv4 address, for example a CubePath VPS. WireGuard needs very few resources; 1 vCPU and 512 MB of RAM are enough for a handful of clients.
- A non-root user with
sudoprivileges and UFW enabled with SSH allowed. - A client device: Windows, macOS, Linux, Android or iOS, with the official WireGuard app or
wireguard-toolsinstalled.
The guide uses these values; change them if they collide with a network you already use:
| Item | Value |
|---|---|
| VPN subnet | 10.8.0.0/24 |
| Server VPN address | 10.8.0.1 |
| First client address | 10.8.0.2 |
| Listen port | 51820/udp |
Step 1 - Installing WireGuard
The WireGuard kernel module ships with the Ubuntu kernel, so you only need the userspace tools. Install them together with qrencode, which you will use later to configure phones:
sudo apt update
sudo apt install wireguard qrencode
Verify the wg tool is available:
wg --version
wireguard-tools v1.0.20210914 - https://git.zx2c4.com/wireguard-tools/
Step 2 - Generating the server keys
Each WireGuard peer has a private key that never leaves the machine and a public key that you share with the other side. Generate the server's key pair in /etc/wireguard, with a restrictive umask so the private key is only readable by root:
sudo -i
cd /etc/wireguard
umask 077
wg genkey | tee server.key | wg pubkey > server.pub
exit
Display both keys; you will need them in the next steps:
sudo cat /etc/wireguard/server.key /etc/wireguard/server.pub
yAnz5TF+lXXJte14tji3zlMNq+hd2rYUIgJBgB3fBmk=
xTIBA5rboUvnH4htodjb6e697QjLERt1NAB4mZqp8Dg=
The first line is the private key, the second the public key.
Step 3 - Enabling IP forwarding
For the server to route traffic from VPN clients to the internet, the kernel must forward packets between interfaces. Enable it permanently with a sysctl drop-in file:
echo 'net.ipv4.ip_forward = 1' | sudo tee /etc/sysctl.d/99-wireguard.conf
sudo sysctl --system
Confirm the value:
sysctl net.ipv4.ip_forward
net.ipv4.ip_forward = 1
Step 4 - Creating the server configuration
Clients' traffic must leave the server with the server's public IP, which requires a NAT (masquerade) rule on the public interface. Find that interface name:
ip route show default
default via 203.0.113.1 dev eth0 proto static
In this example it is eth0; on some servers it is ens3 or similar. Now create the WireGuard configuration:
sudo nano /etc/wireguard/wg0.conf
Paste the following, replacing server_private_key with the content of server.key and eth0 with your interface name:
[Interface]
Address = 10.8.0.1/24
ListenPort = 51820
PrivateKey = server_private_key
PostUp = iptables -t nat -A POSTROUTING -s 10.8.0.0/24 -o eth0 -j MASQUERADE
PostDown = iptables -t nat -D POSTROUTING -s 10.8.0.0/24 -o eth0 -j MASQUERADE
The PostUp and PostDown commands add the NAT rule when the interface starts and remove it when it stops. Client [Peer] sections will be added in Step 7.
Step 5 - Allowing WireGuard through UFW
UFW needs two changes: accept incoming WireGuard packets on UDP 51820, and allow forwarding from the VPN interface to the internet (UFW drops forwarded traffic by default):
sudo ufw allow 51820/udp
sudo ufw route allow in on wg0 out on eth0
Check the rules:
sudo ufw status
To Action From
-- ------ ----
OpenSSH ALLOW Anywhere
51820/udp ALLOW Anywhere
Anywhere on eth0 ALLOW FWD Anywhere on wg0
...
Step 6 - Starting the WireGuard service
wg-quick brings up the interface from wg0.conf, and the wg-quick@ systemd template runs it as a service. Enable and start it:
sudo systemctl enable --now wg-quick@wg0
Check the interface:
sudo wg show
interface: wg0
public key: xTIBA5rboUvnH4htodjb6e697QjLERt1NAB4mZqp8Dg=
private key: (hidden)
listening port: 51820
If the service fails, journalctl -u wg-quick@wg0 shows which line of the configuration it could not apply.
Step 7 - Adding a client
Generate a key pair for the first client. Doing this on the server is convenient for phones, which you will configure by scanning a QR code; for laptops you can also generate the keys on the client and only copy its public key to the server.
sudo -i
mkdir -p /etc/wireguard/clients
cd /etc/wireguard/clients
umask 077
wg genkey | tee client1.key | wg pubkey > client1.pub
exit
Add the client as a peer on the server. Open the server configuration again:
sudo nano /etc/wireguard/wg0.conf
Append this section, replacing client1_public_key with the content of /etc/wireguard/clients/client1.pub:
[Peer]
# client1
PublicKey = client1_public_key
AllowedIPs = 10.8.0.2/32
On the server side, AllowedIPs lists the addresses that this peer is allowed to use as source; each client gets its own /32. Apply the change without dropping connected clients:
sudo systemctl reload wg-quick@wg0
The reload runs wg syncconf, which only updates what changed. sudo wg show now lists the peer.
Next, write the client's configuration file:
sudo nano /etc/wireguard/clients/client1.conf
Replace client1_private_key with the content of client1.key, server_public_key with server.pub and your_server_ip with the server's public IP:
[Interface]
PrivateKey = client1_private_key
Address = 10.8.0.2/32
DNS = 1.1.1.1, 9.9.9.9
[Peer]
PublicKey = server_public_key
Endpoint = your_server_ip:51820
AllowedIPs = 0.0.0.0/0
PersistentKeepalive = 25
On the client side, AllowedIPs = 0.0.0.0/0 means "send all IPv4 traffic through the tunnel". PersistentKeepalive keeps NAT mappings on home routers and mobile networks open so the server can always reach the client.
Step 8 - Connecting the client
Phones (Android and iOS)
Install the WireGuard app, tap + and choose Scan from QR code. Show the configuration as a QR code in your terminal:
sudo qrencode -t ansiutf8 < /etc/wireguard/clients/client1.conf
Scan it, give the tunnel a name and turn it on.
Windows and macOS
Install the WireGuard app from wireguard.com or the Mac App Store, choose Import tunnel(s) from file, and select a copy of client1.conf. Transfer the file over SSH, for example with scp, and delete it from the client's download folder once imported, since it contains a private key.
Linux
Install the tools, copy the file to /etc/wireguard/wg0.conf on the client, and bring the tunnel up:
sudo apt install wireguard
sudo wg-quick up wg0
Step 9 - Testing the VPN
On the server, check that the client completed a handshake:
sudo wg show
peer: 3Q8Z0...client1 public key...=
endpoint: 198.51.100.23:53012
allowed ips: 10.8.0.2/32
latest handshake: 12 seconds ago
transfer: 1.24 MiB received, 8.73 MiB sent
From the client, ping the server's VPN address and check which public IP the internet sees:
ping -c 3 10.8.0.1
curl -4 https://ifconfig.me
The curl command should print your server's public IP, not the client's. On a phone, open any "what is my IP" website instead.
Adding more clients and split tunneling
For each additional device, repeat Step 7 with a new key pair and the next free address (10.8.0.3/32, 10.8.0.4/32 and so on). Never reuse a key pair on two devices: WireGuard identifies peers by public key, and two devices with the same key keep taking the tunnel from each other.
If you only want to reach the VPN network (for example private services on the server) and keep normal browsing on the client's own connection, change AllowedIPs in the client file to the VPN subnet and remove the DNS line:
AllowedIPs = 10.8.0.0/24
Troubleshooting
- No
latest handshakeline on the server. The packets are not arriving. Checksudo ufw statusfor51820/udp, confirm theEndpointIP and port in the client file, and make sure the client uses the server's public key (not its own). - Handshake works but there is no internet. Forwarding or NAT is missing. Verify
sysctl net.ipv4.ip_forwardreturns1, that the interface inPostUpmatchesip route show default, and that theufw route allowrule exists. - Web pages don't load but pings to IPs work. DNS is the problem. Check the
DNSline in the client file. On Linux clients,wg-quickneeds aresolvconfimplementation; if it reportsresolvconf: command not found, installopenresolvor import the file withnmcli connection import type wireguard file wg0.confinstead. - Some sites hang while others work. This is usually an MTU problem on links with extra encapsulation (PPPoE, mobile networks). Add
MTU = 1380to the client's[Interface]section and reconnect.
Conclusion
You now have a WireGuard VPN server on Ubuntu 24.04 that routes clients' traffic through its public IP, with a repeatable procedure to add devices. As next steps, consider adding IPv6 to the tunnel, using the VPN to reach private services such as a Samba share or a database without exposing them to the internet, and keeping the clients directory in an encrypted backup so you can revoke or restore peers.
