· Sysadmin.id · Security · 7 min read
How to Set Up a WireGuard VPN Server on Ubuntu 24.04
A practical guide to running your own WireGuard VPN on Ubuntu 24.04 — key generation, server and client config, IP forwarding, NAT firewall rules, and connecting a phone with a QR code.

WireGuard is the simplest fast VPN you can self-host. It lives in the Linux kernel, uses modern cryptography, and a working server config fits on one screen — no certificate authority, no daemon zoo, no 200-line OpenVPN file.
This guide sets up a WireGuard server on Ubuntu 24.04 that routes a client’s traffic through your VPS — useful for a private exit node, securing traffic on untrusted Wi-Fi, or reaching internal services from anywhere.
By the end you’ll have a running server, one laptop peer, and a phone connected by scanning a QR code.
Prerequisites
Before you start, make sure you have:
- A VPS or server running Ubuntu 24.04 LTS with a public IP
- Root or sudo access
- The server’s public network interface name (usually
eth0orens3) - One or more client devices — a laptop, a phone, or both
Find your interface name with
ip route show default— it’s the name afterdev, e.g.default via 10.0.0.1 dev eth0.
Step 1: Install WireGuard
On Ubuntu 24.04 the tools are in the default repositories:
sudo apt update
sudo apt install -y wireguard wireguard-toolsThe kernel module ships with Ubuntu already, so there’s nothing to compile. Verify the tools are present:
wg --versionStep 2: Generate the Server Keys
WireGuard authenticates peers with public/private key pairs — no passwords, no certificates. Generate the server’s pair:
# Work in the WireGuard config directory with a safe umask
sudo -i
cd /etc/wireguard
umask 077
wg genkey | tee server_private.key | wg pubkey > server_public.keyumask 077 ensures the private key is readable only by root. Look at both keys — you’ll paste them into config files shortly:
cat server_private.key
cat server_public.keyNever share the private key. Only public keys are exchanged between peers.
Step 3: Generate a Client Key Pair
Each client needs its own key pair too. You can generate them on the client device, but for a quick setup it’s fine to make them on the server:
wg genkey | tee client1_private.key | wg pubkey > client1_public.keyKeep these straight — client1_private.key goes on the client, client1_public.key goes in the server config.
Step 4: Create the Server Config
Create /etc/wireguard/wg0.conf. This defines the VPN subnet (10.8.0.0/24), the server’s address inside it, the listening port, and NAT rules so client traffic can reach the internet.
[Interface]
Address = 10.8.0.1/24
ListenPort = 51820
PrivateKey = <SERVER_PRIVATE_KEY>
# Route client traffic out through the public interface (change eth0 if needed)
PostUp = nft add table inet wireguard; nft add chain inet wireguard postrouting { type nat hook postrouting priority 100 \; }; nft add rule inet wireguard postrouting oifname "eth0" masquerade
PostDown = nft delete table inet wireguard
[Peer]
# client1 — laptop
PublicKey = <CLIENT1_PUBLIC_KEY>
AllowedIPs = 10.8.0.2/32Replace <SERVER_PRIVATE_KEY> with the contents of server_private.key and <CLIENT1_PUBLIC_KEY> with client1_public.key. Adjust eth0 to your public interface from the prerequisites.
A few things worth understanding:
Addressis the server’s IP inside the tunnel, not its public IP.ListenPortis the UDP port WireGuard listens on —51820is the convention.AllowedIPsin a[Peer]block means “this peer owns these IPs.” For a single client that’s a/32— its one tunnel address.PostUp/PostDownadd and remove a masquerade (NAT) rule so packets from10.8.0.0/24leave with the server’s public IP and replies find their way back.
Step 5: Enable IP Forwarding
By default Linux won’t forward packets between interfaces. The VPN needs it on. Edit /etc/sysctl.conf and add (or uncomment):
net.ipv4.ip_forward = 1
net.ipv6.conf.all.forwarding = 1Apply it without rebooting:
sudo sysctl -pConfirm it’s active:
sysctl net.ipv4.ip_forward
# net.ipv4.ip_forward = 1Step 6: Open the Firewall Port
If a firewall is active, allow the WireGuard UDP port. With UFW:
sudo ufw allow 51820/udp
sudo ufw reloadIf you manage your VPS firewall in a cloud panel (AWS Security Group, DigitalOcean Cloud Firewall, etc.), add an inbound rule for UDP 51820 there too.
Step 7: Start the Server
WireGuard ships a systemd service template — bring up the wg0 interface and enable it on boot:
sudo systemctl enable --now wg-quick@wg0Check the status:
sudo wg showYou’ll see the interface, its public key, the listening port, and your peer listed with no handshake yet — that’s expected until a client connects.
interface: wg0
public key: <SERVER_PUBLIC_KEY>
private key: (hidden)
listening port: 51820
peer: <CLIENT1_PUBLIC_KEY>
allowed ips: 10.8.0.2/32Step 8: Create the Client Config
On the client side you need a small config file. Create client1.conf:
[Interface]
Address = 10.8.0.2/32
PrivateKey = <CLIENT1_PRIVATE_KEY>
DNS = 1.1.1.1
[Peer]
PublicKey = <SERVER_PUBLIC_KEY>
Endpoint = <SERVER_PUBLIC_IP>:51820
AllowedIPs = 0.0.0.0/0
PersistentKeepalive = 25Fill in the blanks:
<CLIENT1_PRIVATE_KEY>— contents ofclient1_private.key<SERVER_PUBLIC_KEY>— contents ofserver_public.key<SERVER_PUBLIC_IP>— your VPS public IP
Two important lines:
AllowedIPs = 0.0.0.0/0routes all the client’s traffic through the VPN (full-tunnel). For split-tunnel — only reaching internal subnets — set this to something like10.8.0.0/24, 10.0.0.0/8instead.PersistentKeepalive = 25keeps the connection alive through NAT and home routers by sending a packet every 25 seconds. Useful for clients behind a router; harmless otherwise.
Step 9: Connect a Laptop
Copy client1.conf to the client machine and bring the tunnel up.
On Linux:
sudo cp client1.conf /etc/wireguard/wg0.conf
sudo wg-quick up wg0On macOS or Windows, install the official WireGuard app, import the client1.conf file, and click Activate.
Verify your traffic is now exiting through the VPN:
curl ifconfig.me
# should print your SERVER's public IP, not the client'sBack on the server, sudo wg show will now display a recent handshake and transfer counters for the peer — that’s a live connection.
Step 10: Connect a Phone with a QR Code
You don’t want to type a private key on a phone. WireGuard can render a config as a QR code that the mobile app scans directly.
First make a phone config — generate a second key pair and add the peer to the server (see Step 3 and the [Peer] block in Step 4, using 10.8.0.3/32). Then, on the server, install the QR tool and render the phone’s config:
sudo apt install -y qrencode
qrencode -t ansiutf8 < phone.confA QR code prints right in the terminal. Open the WireGuard app on your phone → + → Scan from QR code → point it at the screen. The tunnel imports instantly. Toggle it on and your phone’s traffic runs through the VPN.
Reload the server after adding peers so it picks up the new
[Peer]blocks:sudo systemctl reload wg-quick@wg0— reload re-applies config without dropping existing connections.
Managing Peers
| Task | Command |
|---|---|
| Show live status & handshakes | sudo wg show |
| Bring the tunnel up | sudo wg-quick up wg0 |
| Bring the tunnel down | sudo wg-quick down wg0 |
| Reload after editing config | sudo systemctl reload wg-quick@wg0 |
| Restart the service | sudo systemctl restart wg-quick@wg0 |
To add a client, generate its key pair, add a [Peer] block to wg0.conf with a fresh /32 address, and reload. To revoke one, delete its [Peer] block and reload — the key stops working immediately.
Common Issues and Fixes
Handshake never completes
Almost always the firewall. Confirm UDP 51820 is open both in UFW and in your cloud provider’s firewall. WireGuard is UDP, not TCP — a rule for the wrong protocol silently drops it.
Connected, but no internet
IP forwarding or NAT is missing. Re-check sysctl net.ipv4.ip_forward returns 1, and that the PostUp masquerade rule names your real public interface (oifname "eth0" must match ip route show default).
Works on Wi-Fi, drops on mobile data
Add PersistentKeepalive = 25 to the client’s [Peer] block. Carrier-grade NAT closes idle UDP mappings; the keepalive holds them open.
DNS not resolving through the tunnel
Make sure the client config has a DNS = line (e.g. 1.1.1.1). Without it, full-tunnel clients route packets through the VPN but still try to resolve names against an unreachable local resolver.
wg-quick fails with “resolvconf: command not found”
Install it: sudo apt install -y openresolv — wg-quick uses it to apply the DNS = setting.
Summary
Here’s what you built:
- Installed WireGuard from the Ubuntu repositories
- Generated server and client key pairs
- Wrote a
wg0.confserver config with NAT masquerading - Enabled IP forwarding and opened the firewall port
- Started the service with systemd and confirmed it was listening
- Connected a laptop with a full-tunnel client config
- Added a phone by scanning a QR code
You now run your own VPN — fast, private, and entirely under your control. Adding more devices is just another key pair and a [Peer] block away.
Need a hardened WireGuard setup, a multi-site mesh, or help locking down your servers? Get in touch — I’m happy to help.
- wireguard
- vpn
- ubuntu
- linux
- networking
- security



