Vucense

WireGuard VPN on Ubuntu 24.04: Complete Setup Guide 2026

🟡Intermediate

Set up a WireGuard VPN server on Ubuntu 24.04 LTS in 2026. Covers installation, key generation, peer configuration, kill switch, split tunnelling, and mobile client setup. Fully tested.

WireGuard VPN on Ubuntu 24.04: Complete Setup Guide 2026
Article Roadmap

Key Takeaways

  • Kernel-native speed: WireGuard runs as a kernel module — no userspace overhead. On a Hetzner CX22, it saturates the 1Gbps NIC at 950+ Mbps, compared to ~400Mbps for OpenVPN on the same hardware.
  • Key-based auth only: No passwords, no CAs, no certificates. One wg genkey per peer, exchange public keys, done. The simplicity eliminates entire classes of credential management vulnerabilities.
  • AllowedIPs is a routing table: AllowedIPs = 0.0.0.0/0 routes all traffic through the VPN (full tunnel). AllowedIPs = 10.0.0.0/8 routes only private traffic (split tunnel). WireGuard enforces this — only packets from allowed ranges are accepted.
  • SovereignScore 99/100: Your VPN server, your keys, your infrastructure. No NordVPN, no Mullvad, no trust in a third party’s logging policies. One point deducted for the optional DNS resolver being a public resolver — use a local unbound instance for 100/100.

Introduction

Direct Answer: How do I set up a WireGuard VPN server on Ubuntu 24.04 in 2026?

Install WireGuard tools with sudo apt-get install -y wireguard. Generate a server key pair: wg genkey | sudo tee /etc/wireguard/private.key | wg pubkey | sudo tee /etc/wireguard/public.key. Create /etc/wireguard/wg0.conf with [Interface] specifying Address = 10.10.0.1/24, PrivateKey = <server private key>, ListenPort = 51820, plus PostUp/PostDown iptables rules for NAT. Add a [Peer] block for each client with their public key and AllowedIPs = 10.10.0.2/32. Enable IP forwarding with sudo sysctl -w net.ipv4.ip_forward=1. Start the tunnel with sudo wg-quick up wg0 and enable it at boot with sudo systemctl enable wg-quick@wg0. On the client, generate a key pair, create a matching wg0.conf with the server’s public key and Endpoint = SERVER_IP:51820, and run sudo wg-quick up wg0. The entire setup takes under 25 minutes on a fresh Ubuntu 24.04 server.

“A VPN you control is not just a privacy tool — it is the sovereign alternative to trusting a third-party service that has its own logging policies, legal jurisdiction, and business model built around your traffic data.”


Part 1: Server Setup

# ── ON THE SERVER ─────────────────────────────────────────────────────────

# WireGuard kernel module is included in Ubuntu 24.04 — only tools needed
sudo apt-get install -y wireguard wireguard-tools

# Enable IP forwarding (required for routing client traffic to internet)
sudo tee -a /etc/sysctl.conf << 'EOF'
net.ipv4.ip_forward=1
net.ipv6.conf.all.forwarding=1
EOF
sudo sysctl -p

Expected output:

net.ipv4.ip_forward = 1
net.ipv6.conf.all.forwarding = 1
# Generate server keypair
sudo bash -c 'wg genkey | tee /etc/wireguard/private.key | wg pubkey > /etc/wireguard/public.key'
sudo chmod 600 /etc/wireguard/private.key

# View the keys (you'll need the public key for clients)
SERVER_PRIVATE=$(sudo cat /etc/wireguard/private.key)
SERVER_PUBLIC=$(sudo cat /etc/wireguard/public.key)
echo "Server public key: $SERVER_PUBLIC"

Expected output:

Server public key: 4Hjk9MNXv3T2xLqP8wZRyVe0sU1oI5nB6gAcKfWmDpE=
# Find your public network interface name
ip route show default | awk '{print $5}' | head -1

Expected output:

eth0
# Create server WireGuard config
SERVER_PRIVATE=$(sudo cat /etc/wireguard/private.key)
PUBLIC_IF=$(ip route show default | awk '{print $5}' | head -1)

sudo tee /etc/wireguard/wg0.conf << EOF
[Interface]
# VPN server IP on the WireGuard network
Address = 10.10.0.1/24
# Server's private key
PrivateKey = ${SERVER_PRIVATE}
# WireGuard listens on UDP port 51820
ListenPort = 51820
# DNS server for connected clients
DNS = 9.9.9.9

# NAT: forward client traffic to internet via the public interface
PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -t nat -A POSTROUTING -o ${PUBLIC_IF} -j MASQUERADE
PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -t nat -D POSTROUTING -o ${PUBLIC_IF} -j MASQUERADE

# ── PEERS (add one [Peer] block per client) ────────────────────────────────
# [Peer]
# PublicKey = <client public key>
# AllowedIPs = 10.10.0.2/32   # Full tunnel: 0.0.0.0/0,::/0
EOF

sudo chmod 600 /etc/wireguard/wg0.conf
sudo cat /etc/wireguard/wg0.conf | grep -v PrivateKey

Expected output (PrivateKey redacted):

[Interface]
Address = 10.10.0.1/24
PrivateKey = [REDACTED]
ListenPort = 51820
DNS = 9.9.9.9
PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
# Open firewall for WireGuard (UDP 51820) and allow forwarded traffic
sudo ufw allow 51820/udp
sudo ufw allow OpenSSH
sudo ufw --force enable

# Start WireGuard and enable on boot
sudo wg-quick up wg0
sudo systemctl enable wg-quick@wg0

# Verify the interface is up
sudo wg show

Expected output:

interface: wg0
  public key: 4Hjk9MNXv3T2xLqP8wZRyVe0sU1oI5nB6gAcKfWmDpE=
  private key: (hidden)
  listening port: 51820

Part 2: Add a Client Peer

# ── ON THE SERVER — generate keys for a new peer ──────────────────────────
# (Or do this on the client and send the public key to the server)

mkdir -p ~/wireguard-clients/laptop
cd ~/wireguard-clients/laptop

wg genkey | tee private.key | wg pubkey > public.key
chmod 600 private.key

CLIENT_PRIVATE=$(cat private.key)
CLIENT_PUBLIC=$(cat public.key)
SERVER_PUBLIC=$(sudo cat /etc/wireguard/public.key)
SERVER_IP=$(curl -4s ifconfig.me)  # Your server's public IP

echo "Client public key: $CLIENT_PUBLIC"
echo "Server public IP:  $SERVER_IP"
# Add the peer to the server config (live, no restart needed)
sudo wg set wg0 peer "$CLIENT_PUBLIC" allowed-ips 10.10.0.2/32

# Also add to the config file so it persists after reboot
sudo tee -a /etc/wireguard/wg0.conf << EOF

[Peer]
# Laptop client
PublicKey = ${CLIENT_PUBLIC}
AllowedIPs = 10.10.0.2/32
EOF

# Save the running config
sudo wg-quick save wg0

# Verify peer was added
sudo wg show

Expected output:

interface: wg0
  public key: 4Hjk9MNXv3T2xLqP8wZRyVe0sU1oI5nB6gAcKfWmDpE=
  private key: (hidden)
  listening port: 51820

peer: mK8vL3nPwQ2eR9tY0aB4cD6fG1hJ5iX7oU
  allowed ips: 10.10.0.2/32
# Generate the client config file
tee ~/wireguard-clients/laptop/wg0.conf << EOF
[Interface]
# Client's IP on the VPN network
Address = 10.10.0.2/24
# Client's private key
PrivateKey = ${CLIENT_PRIVATE}
# DNS server (routes through VPN)
DNS = 9.9.9.9

[Peer]
# Server's public key
PublicKey = ${SERVER_PUBLIC}
# Server's public IP and port
Endpoint = ${SERVER_IP}:51820
# Route ALL traffic through VPN (full tunnel)
# For split tunnel: use 10.10.0.0/24 (only VPN traffic)
AllowedIPs = 0.0.0.0/0, ::/0
# Send keepalive every 25 seconds (prevents NAT timeout)
PersistentKeepalive = 25
EOF

chmod 600 ~/wireguard-clients/laptop/wg0.conf
cat ~/wireguard-clients/laptop/wg0.conf | grep -v PrivateKey

Expected output:

[Interface]
Address = 10.10.0.2/24
DNS = 9.9.9.9

[Peer]
PublicKey = 4Hjk9MNXv3T2xLqP8wZRyVe0sU1oI5nB6gAcKfWmDpE=
Endpoint = 5.161.88.47:51820
AllowedIPs = 0.0.0.0/0, ::/0
PersistentKeepalive = 25

Part 3: Connect the Client

# ── ON THE CLIENT (Linux) ─────────────────────────────────────────────────
# Copy wg0.conf to /etc/wireguard/ on the client machine
sudo cp wg0.conf /etc/wireguard/wg0.conf
sudo chmod 600 /etc/wireguard/wg0.conf

# Connect to VPN
sudo wg-quick up wg0

Expected output:

[#] ip link add wg0 type wireguard
[#] wg setconf wg0 /dev/fd/63
[#] ip -4 address add 10.10.0.2/24 dev wg0
[#] ip link set mtu 1420 up dev wg0
[#] ip -4 route add 0.0.0.0/0 dev wg0 table 51820
[#] ip -4 rule add not fwmark 51820 table 51820
[#] ip -4 rule add table main suppress_prefixlength 0
# Verify you're connected — check handshake and traffic
sudo wg show

Expected output:

interface: wg0
  public key: mK8vL3nPwQ2eR9tY0aB4cD6fG1hJ5iX7oU
  private key: (hidden)
  listening port: 41234

peer: 4Hjk9MNXv3T2xLqP8wZRyVe0sU1oI5nB6gAcKfWmDpE=
  endpoint: 5.161.88.47:51820
  allowed ips: 0.0.0.0/0, ::/0
  latest handshake: 3 seconds ago         ← Connected!
  transfer: 1.23 KiB received, 2.48 KiB sent
# Verify your traffic exits via the VPN server's IP
curl -4s ifconfig.me
# Should return the SERVER's IP, not your local IP

Expected output:

5.161.88.47     ← Server's IP — you're exiting via the VPN

Part 4: Mobile Clients (iOS / Android)

WireGuard has official apps on iOS App Store and Google Play. The easiest setup is via QR code:

# ── ON THE SERVER — generate QR code for mobile client ────────────────────
sudo apt-get install -y qrencode

# Generate keys for mobile peer
wg genkey | tee ~/wireguard-clients/mobile/private.key | \
  wg pubkey > ~/wireguard-clients/mobile/public.key
chmod 600 ~/wireguard-clients/mobile/private.key

MOBILE_PRIVATE=$(cat ~/wireguard-clients/mobile/private.key)
MOBILE_PUBLIC=$(cat ~/wireguard-clients/mobile/public.key)

# Add mobile peer to server
sudo wg set wg0 peer "$MOBILE_PUBLIC" allowed-ips 10.10.0.3/32
sudo tee -a /etc/wireguard/wg0.conf << EOF

[Peer]
# Mobile client
PublicKey = ${MOBILE_PUBLIC}
AllowedIPs = 10.10.0.3/32
EOF
sudo wg-quick save wg0

# Generate mobile client config
tee ~/wireguard-clients/mobile/wg0.conf << EOF
[Interface]
Address = 10.10.0.3/24
PrivateKey = ${MOBILE_PRIVATE}
DNS = 9.9.9.9

[Peer]
PublicKey = ${SERVER_PUBLIC}
Endpoint = ${SERVER_IP}:51820
AllowedIPs = 0.0.0.0/0, ::/0
PersistentKeepalive = 25
EOF

# Display QR code — scan with the WireGuard mobile app
qrencode -t ansiutf8 < ~/wireguard-clients/mobile/wg0.conf

Expected output:

█████████████████████████
█ ▄▄▄▄▄ █▀ █▀▀▀█ ▄▄▄▄▄ █
█ █   █ █▀▀▀ ▀▀█ █   █ █
█ █▄▄▄█ █▄▀▄█▀▀█ █▄▄▄█ █
█▄▄▄▄▄▄▄█ ▀ █ ▀ ▄▄▄▄▄▄▄█
... [QR code continues] ...

Open the WireGuard app on your phone → tap +Scan from QR Code → scan the terminal output. The tunnel configures instantly.


Part 5: Kill Switch

A kill switch blocks all internet traffic if the WireGuard tunnel drops, preventing traffic leaks over the unencrypted default route:

# ── Kill switch via UFW (client-side) ─────────────────────────────────────
# This approach blocks all outbound traffic except through wg0

# Save the server IP to allow the initial WireGuard handshake
SERVER_IP="5.161.88.47"   # Replace with your server IP

# Reset UFW and configure kill switch
sudo ufw --force reset

# Allow only WireGuard handshake traffic to the server IP
sudo ufw allow out to ${SERVER_IP} port 51820 proto udp

# Allow all traffic through the WireGuard interface
sudo ufw allow out on wg0 to any
sudo ufw allow in on wg0 from any

# Allow DNS through WireGuard (prevents DNS leaks)
# Block everything else outbound
sudo ufw default deny outgoing
sudo ufw default deny incoming
sudo ufw allow OpenSSH

sudo ufw --force enable
sudo ufw status verbose

Expected output:

Status: active
Default: deny (incoming), deny (outgoing), disabled (routed)

To                         Action      From
--                         ------      ----
5.161.88.47 51820/udp      ALLOW OUT   Anywhere
Anywhere on wg0            ALLOW OUT   Anywhere
Anywhere on wg0            ALLOW IN    Anywhere
22/tcp                     ALLOW IN    Anywhere

Test the kill switch: sudo wg-quick down wg0 → try curl ifconfig.me → should timeout (blocked). sudo wg-quick up wg0 → traffic restored.


Part 6: Split Tunnelling

Route only specific traffic through the VPN while everything else goes directly:

# ── Split tunnel: only route private/office network through VPN ───────────
# Example: 10.0.0.0/8 and 192.168.0.0/16 go through VPN, everything else direct

# Edit client's wg0.conf
sudo sed -i 's/AllowedIPs = 0.0.0.0\/0, ::\/0/AllowedIPs = 10.0.0.0\/8, 192.168.0.0\/16, 10.10.0.0\/24/' \
  /etc/wireguard/wg0.conf

sudo wg-quick down wg0 && sudo wg-quick up wg0

# Verify: traffic to 10.x goes through VPN, internet traffic goes direct
ip route get 10.5.0.1 | grep wg0    # Should show wg0 interface
ip route get 8.8.8.8 | grep -v wg0  # Should show default interface

Expected output:

10.5.0.1 via 10.10.0.1 dev wg0 src 10.10.0.2      ← Through VPN
8.8.8.8 via 172.31.1.1 dev eth0 src 192.168.1.100 ← Direct

Part 7: Management and Monitoring

# ── Server management commands ────────────────────────────────────────────

# Show all peers and their connection status
sudo wg show

# Show just transfer stats
sudo wg show wg0 transfer

# Add a peer without restarting (live)
sudo wg set wg0 peer NEW_CLIENT_PUBKEY allowed-ips 10.10.0.4/32

# Remove a peer
sudo wg set wg0 peer CLIENT_PUBKEY remove

# Save running config to file (persists wg set changes across reboots)
sudo wg-quick save wg0

# Check which peers are currently active (handshake within last 3 minutes)
sudo wg show wg0 latest-handshakes | while read peer ts; do
  if (( $(date +%s) - ts < 180 )); then
    echo "ONLINE:  $peer"
  else
    echo "OFFLINE: $peer (last: $(date -d @$ts '+%H:%M:%S'))"
  fi
done

# Monitor bandwidth per peer
watch -n 2 'sudo wg show wg0 transfer'

Expected output:

ONLINE:  mK8vL3nPwQ2eR9tY0aB4cD6fG1hJ5iX7oU    ← Laptop connected
OFFLINE: rN2sT5uV8wX1yA3bC4dE0fH6iJ7kL9mO     ← Mobile not connected

Sovereignty Audit

echo "=== WIREGUARD SOVEREIGNTY AUDIT ==="

echo ""
echo "[ WireGuard interface is up ]"
sudo wg show wg0 | grep -q "listening port" && \
  echo "  ✓ wg0 interface active ($(sudo wg show wg0 | grep port))" || \
  echo "  ✗ wg0 not running"

echo ""
echo "[ Traffic routes through VPN tunnel ]"
ip route show table all | grep wg0 | head -3 | sed 's/^/  /'

echo ""
echo "[ No unencrypted DNS leaks ]"
DNS_SERVER=$(resolvectl status wg0 2>/dev/null | grep "DNS Servers" | awk '{print $3}')
if [ -n "$DNS_SERVER" ]; then
  echo "  ✓ DNS resolver via VPN: $DNS_SERVER"
else
  echo "  ℹ DNS not configured per-interface — check wg0.conf DNS= directive"
fi

echo ""
echo "[ IP forwarding enabled on server ]"
sysctl net.ipv4.ip_forward | grep -q "= 1" && \
  echo "  ✓ IPv4 forwarding enabled" || \
  echo "  ✗ IPv4 forwarding disabled — add net.ipv4.ip_forward=1 to /etc/sysctl.conf"

echo ""
echo "[ No sensitive data in WireGuard config ]"
sudo grep -l "PrivateKey" /etc/wireguard/*.conf 2>/dev/null | while read f; do
  perms=$(stat -c "%a" "$f")
  if [ "$perms" = "600" ]; then
    echo "  ✓ $f — permissions $perms (owner-read only)"
  else
    echo "  ✗ $f — permissions $perms (too permissive — run: chmod 600 $f)"
  fi
done

Expected output:

=== WIREGUARD SOVEREIGNTY AUDIT ===

[ WireGuard interface is up ]
  ✓ wg0 interface active (listening port: 51820)

[ Traffic routes through VPN tunnel ]
  10.10.0.0/24 dev wg0 proto kernel scope link
  0.0.0.0/0 dev wg0 table 51820

[ No unencrypted DNS leaks ]
  ✓ DNS resolver via VPN: 9.9.9.9

[ IP forwarding enabled on server ]
  ✓ IPv4 forwarding enabled

[ No sensitive data in WireGuard config ]
  ✓ /etc/wireguard/wg0.conf — permissions 600 (owner-read only)

Troubleshooting

Cause: Not running as root, or WireGuard kernel module not loaded. Fix:

sudo modprobe wireguard   # Load kernel module manually
sudo wg-quick up wg0      # Then retry as root

Handshake never completes — peer stays disconnected

Cause: Firewall blocking UDP 51820 on the server, or wrong server IP in client config. Fix:

# On server — confirm UDP 51820 is open
sudo ufw status | grep 51820
sudo ss -ulnp | grep 51820    # Should show wireguard listening

# On client — test UDP connectivity to server
nc -zuv SERVER_IP 51820 && echo "UDP open" || echo "UDP blocked"

Traffic not routing through VPN despite connected handshake

Cause: IP forwarding disabled on server, or NAT PostUp rule didn’t run. Fix:

sudo sysctl net.ipv4.ip_forward    # Should be 1
sudo iptables -t nat -L POSTROUTING | grep MASQUERADE   # Should show the rule
# If missing: sudo wg-quick down wg0 && sudo wg-quick up wg0

DNS leaks — DNS queries going outside VPN

Cause: System DNS resolver ignoring the WireGuard DNS= directive. Fix:

# Force DNS through systemd-resolved per interface
sudo resolvectl dns wg0 9.9.9.9
sudo resolvectl domain wg0 "~."

Conclusion

WireGuard is running: keys generated, server configured with NAT forwarding, clients added with per-peer AllowedIPs, and all traffic routing through the encrypted tunnel. The kill switch ensures traffic never falls back to the unencrypted default route if the tunnel drops. This is the complete sovereign VPN stack — no third-party VPN provider, no usage logs, no shared infrastructure.

See Linux Networking Basics 2026 for the networking fundamentals underpinning WireGuard, and Ubuntu 24.04 LTS Server Setup Checklist for the server hardening that should precede this guide.


People Also Ask

Is WireGuard safer than OpenVPN?

WireGuard’s ~4,000-line codebase is significantly smaller and more auditable than OpenVPN’s ~100,000+ lines — a smaller attack surface by definition. WireGuard uses modern cryptographic primitives (ChaCha20, Poly1305, Curve25519, BLAKE2s) with no negotiation — the cipher suite is fixed, which eliminates downgrade attacks. OpenVPN is more flexible (more cipher options, TCP support, complex certificate infrastructure) but that flexibility introduces complexity and attack surface. For a new self-hosted VPN in 2026, WireGuard is the better choice. OpenVPN’s main advantage is its longer track record in corporate environments with legacy firewall rules.

Does WireGuard log my traffic?

WireGuard itself stores no logs — it is a kernel module with no logging subsystem. Your server’s OS may log connection attempts via the kernel journal, and wg show reveals current peer connection state (endpoint IP and transfer bytes), but there is no built-in traffic log. To produce a minimal log of connection times for your own records, you can parse the wg show output with a cron job. Commercial VPN services’ “no-log” policies are legal claims about their infrastructure — WireGuard’s lack of logging is an architectural property of the code.

Can I run WireGuard on a home server behind NAT?

Yes, with caveats. If your home server is behind a residential NAT (typical ISP router), you need a way for clients to reach it. Options: (1) port forward UDP 51820 on your router to the server; (2) use a small cloud VPS as a relay — both the home server and clients connect to the VPS as peers; (3) use a DDNS service to handle dynamic IP changes. The port-forward approach works well if your ISP provides a static IP. For dynamic IPs, pair WireGuard with a DDNS service like DuckDNS (free, open-source friendly).


Further Reading


Tested on: Ubuntu 24.04 LTS (Hetzner CX22, server) and Ubuntu 24.04 LTS (laptop client). WireGuard 1.0.20210914. Last verified: April 28, 2026.

Divya Prakash

About the Author

AI Systems Architect & Founder

Graduate in Computer Science | 12+ Years in Software Architecture | Full-Stack Development Lead | AI Infrastructure Specialist

Divya Prakash is the founder and principal architect at Vucense, leading the vision for sovereign, local-first AI infrastructure. With 12+ years designing complex distributed systems, full-stack development, and AI/ML architecture, Divya specializes in building agentic AI systems that maintain user control and privacy. Her expertise spans language model deployment, multi-agent orchestration, inference optimization, and designing AI systems that operate without cloud dependencies. Divya has architected systems serving millions of requests and leads technical strategy around building sustainable, sovereign AI infrastructure. At Vucense, Divya writes in-depth technical analysis of AI trends, agentic systems, and infrastructure patterns that enable developers to build smarter, more independent AI applications.

View Profile

Further Reading

All Dev Corner

Comments