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

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 eth0 or ens3)
  • One or more client devices — a laptop, a phone, or both

Find your interface name with ip route show default — it’s the name after dev, 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-tools

The kernel module ships with Ubuntu already, so there’s nothing to compile. Verify the tools are present:

wg --version

Step 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.key

umask 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.key

Never 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.key

Keep 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/32

Replace <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:

  • Address is the server’s IP inside the tunnel, not its public IP.
  • ListenPort is the UDP port WireGuard listens on — 51820 is the convention.
  • AllowedIPs in a [Peer] block means “this peer owns these IPs.” For a single client that’s a /32 — its one tunnel address.
  • PostUp/PostDown add and remove a masquerade (NAT) rule so packets from 10.8.0.0/24 leave 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 = 1

Apply it without rebooting:

sudo sysctl -p

Confirm it’s active:

sysctl net.ipv4.ip_forward
# net.ipv4.ip_forward = 1

Step 6: Open the Firewall Port

If a firewall is active, allow the WireGuard UDP port. With UFW:

sudo ufw allow 51820/udp
sudo ufw reload

If 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@wg0

Check the status:

sudo wg show

You’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/32

Step 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 = 25

Fill in the blanks:

  • <CLIENT1_PRIVATE_KEY> — contents of client1_private.key
  • <SERVER_PUBLIC_KEY> — contents of server_public.key
  • <SERVER_PUBLIC_IP> — your VPS public IP

Two important lines:

  • AllowedIPs = 0.0.0.0/0 routes all the client’s traffic through the VPN (full-tunnel). For split-tunnel — only reaching internal subnets — set this to something like 10.8.0.0/24, 10.0.0.0/8 instead.
  • PersistentKeepalive = 25 keeps 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 wg0

On 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's

Back 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.conf

A 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

TaskCommand
Show live status & handshakessudo wg show
Bring the tunnel upsudo wg-quick up wg0
Bring the tunnel downsudo wg-quick down wg0
Reload after editing configsudo systemctl reload wg-quick@wg0
Restart the servicesudo 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 openresolvwg-quick uses it to apply the DNS = setting.


Summary

Here’s what you built:

  1. Installed WireGuard from the Ubuntu repositories
  2. Generated server and client key pairs
  3. Wrote a wg0.conf server config with NAT masquerading
  4. Enabled IP forwarding and opened the firewall port
  5. Started the service with systemd and confirmed it was listening
  6. Connected a laptop with a full-tunnel client config
  7. 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
Share:

Written by

tox — Sysadmin & DevOps freelancer

I keep Linux servers, cloud infrastructure, and deployments running for startups and developers worldwide — the same work this guide covers. 99% job success across 100+ Upwork contracts, 10,000+ hours.

Back to Blog

Related Posts

View All Posts »
How to Install Docker on Ubuntu 24.04 LTS

How to Install Docker on Ubuntu 24.04 LTS

A complete step-by-step guide to installing Docker Engine on Ubuntu 24.04 LTS (Noble Numbat) — from adding the official repository to running your first container and setting up Docker Compose.