WireGuard VPN server securely routing laptop and phone traffic to a private network
← Back
wireguard

How to Set Up a WireGuard VPN Server on Ubuntu

enim · Apr 13, 2026 · 5 min read · Updated: Aug 29, 2026
TL;DR: Install wireguard, enable forwarding, create one server keypair and one keypair per client, configure wg0, add narrowly scoped NAT rules, allow UDP 51820, and enable wg-quick@wg0 at boot.

A WireGuard server is a small encrypted network interface, not an account system or web dashboard. Each device has its own key and tunnel IP; the server identifies it by public key and routes only the addresses assigned to that peer.

This guide builds a native server on Ubuntu 24.04. It is deliberately separate from the WireGuard Docker Compose guide: choose this path for the smallest dependency chain and direct host-network control. A self-hosted VPN hides traffic from local Wi-Fi or an ISP, but moves trust to the VPS provider and destination services. It does not make browsing anonymous.

WireGuard server configuration at a glance

Component Address Purpose
Ubuntu server wg0 10.66.66.1/24 VPN gateway
Laptop peer 10.66.66.2/32 First client
Optional IPv6 tunnel fd42:42:42::/64 Private IPv6 inside the tunnel
Public endpoint vpn.example.com:51820/udp Client entry point

The server file is /etc/wireguard/wg0.conf: one [Interface] section followed by one [Peer] section per device. AllowedIPs acts as both a routing selector and an incoming-source allowlist, so each client needs a unique /32 IPv4 address and, when used, a unique /128 IPv6 address.

Prerequisites

If you are choosing a host, use the VPS comparison. Find the public interface instead of guessing it:

ip route show default

The examples use ens3; replace it if your output differs.

Install WireGuard on Ubuntu

Current Linux kernels support WireGuard directly. Install its tools plus qrencode for mobile provisioning:

sudo apt update
sudo apt install --yes wireguard qrencode
wg --version
sudo install -d -m 0700 /etc/wireguard

The official installation page lists packages for other operating systems.

Generate server and client keys

Use a restrictive umask. Never put a private key in shell history, logs, screenshots, or the other peer's configuration.

umask 077
wg genkey | sudo tee /etc/wireguard/server.key \
  | wg pubkey | sudo tee /etc/wireguard/server.pub >/dev/null
sudo chmod 600 /etc/wireguard/server.key
sudo cat /etc/wireguard/server.pub

Generate the laptop keypair on the laptop when possible:

umask 077
wg genkey | tee laptop.key | wg pubkey > laptop.pub

Copy only laptop.pub to the server. Keeping the client private key on the client limits damage if the server is compromised.

Enable IPv4 and IPv6 forwarding

sudo tee /etc/sysctl.d/99-wireguard.conf >/dev/null <<'EOF'
net.ipv4.ip_forward=1
net.ipv6.conf.all.forwarding=1
EOF
sudo sysctl --system
sysctl net.ipv4.ip_forward net.ipv6.conf.all.forwarding

IPv6 forwarding does not automatically provide public IPv6 to clients. Clean routed IPv6 requires a provider-routed prefix. The ULA range below works privately inside the tunnel; do not add ::/0 on clients until routed IPv6 or deliberate NAT66 has been tested.

Create /etc/wireguard/wg0.conf

Open the file with sudo vim /etc/wireguard/wg0.conf and insert the real keys:

[Interface]
Address = 10.66.66.1/24, fd42:42:42::1/64
ListenPort = 51820
PrivateKey = <SERVER_PRIVATE_KEY>
PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -A FORWARD -o %i -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT; iptables -t nat -A POSTROUTING -s 10.66.66.0/24 -o ens3 -j MASQUERADE
PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -D FORWARD -o %i -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT; iptables -t nat -D POSTROUTING -s 10.66.66.0/24 -o ens3 -j MASQUERADE

[Peer]
# laptop
PublicKey = <LAPTOP_PUBLIC_KEY>
AllowedIPs = 10.66.66.2/32, fd42:42:42::2/128

Ubuntu 24.04's iptables command uses the nftables backend. Scoping MASQUERADE to the VPN subnet avoids matching unrelated host traffic. Prefer routed IPv6; add matching ip6tables NAT66 rules only when that tradeoff is intentional and the server uplink works.

Protect and validate the file:

sudo chmod 600 /etc/wireguard/wg0.conf
sudo wg-quick strip /etc/wireguard/wg0.conf >/dev/null

Open the firewall and start WireGuard

sudo ufw allow 51820/udp comment 'WireGuard'
sudo ufw status numbered
sudo systemctl enable --now wg-quick@wg0
sudo systemctl status wg-quick@wg0 --no-pager
sudo wg show

Add the same UDP rule to the VPS provider firewall. Do not open TCP: WireGuard uses UDP and stays silent to unauthenticated packets.

Create the laptop configuration

[Interface]
Address = 10.66.66.2/32, fd42:42:42::2/128
PrivateKey = <LAPTOP_PRIVATE_KEY>
DNS = 1.1.1.1

[Peer]
PublicKey = <SERVER_PUBLIC_KEY>
Endpoint = vpn.example.com:51820
AllowedIPs = 0.0.0.0/0
PersistentKeepalive = 25

The WireGuard quick start describes 25 seconds as a sensible keepalive interval for peers behind NAT; omit it when it is unnecessary. The configuration above is an IPv4 full tunnel. Add ::/0 only after server IPv6 is verified.

Install the official client and import the file. On Linux:

sudo install -m 600 wg0.conf /etc/wireguard/wg0.conf
sudo wg-quick up wg0

For a phone, generate a separate peer and display its configuration only in a trusted terminal:

qrencode -t ansiutf8 < phone.conf

The QR code contains the phone's private key. Never paste it into chat, forward it to monitoring, or display it during screen sharing.

Full tunnel versus split tunnel

The client AllowedIPs decides what enters WireGuard:

# IPv4 full tunnel
AllowedIPs = 0.0.0.0/0

# Full dual stack, only after IPv6 works
AllowedIPs = 0.0.0.0/0, ::/0

# Split tunnel for VPN and private application networks
AllowedIPs = 10.66.66.0/24, 10.20.0.0/16, fd42:42:42::/64

Split tunneling is usually right for private dashboards such as Nginx Proxy Manager, n8n, or Vaultwarden. A full tunnel also makes the VPS the internet exit.

Add or revoke peers without dropping the tunnel

Append another [Peer] block with a unique key and address, then reload:

sudo systemctl reload wg-quick@wg0
sudo wg show

Peer changes reload without tearing down the interface. Changes to Address, PostUp, or PostDown need a restart. To revoke a lost device, remove its peer block and reload immediately.

Back up /etc/wireguard as secret material. Encrypt every off-site copy; the encrypted backup guide covers that workflow.

Test the tunnel

# Client
ip address show wg0
ip route get 10.66.66.1
ping -c 3 10.66.66.1
curl -4 https://icanhazip.com

# Server
sudo wg show

For a full tunnel, the exit address should be the VPS IPv4. Also test DNS. For a split tunnel, confirm an unrelated public destination still uses the client's normal connection.

Troubleshooting

Symptom Check Fix
No handshake sudo wg show; sudo tcpdump -ni any udp port 51820 Correct endpoint/keys and allow UDP in both firewalls
Handshake, no internet sysctl net.ipv4.ip_forward; sudo iptables -t nat -S Enable forwarding and correct ens3 in the NAT rule
Private LAN unreachable Compare routes and both AllowedIPs lists Add the subnet and a return route or intentional NAT
Some sites hang ping -M do -s 1360 1.1.1.1 Try MTU = 1380 on the client
Mobile stops after idling Inspect the last handshake Add PersistentKeepalive = 25 on that client
DNS fails but IPs work resolvectl status Use a resolver reachable through the selected routes
IPv4 works, IPv6 fails ip -6 route; test curl -6 Remove ::/0 until IPv6 routing is complete
Fails after reboot systemctl is-enabled wg-quick@wg0 Enable and start the service

The official Ubuntu troubleshooting checklist likewise starts with keys, AllowedIPs, routes, addresses, and forwarding.

Security checklist and next step

  • Use one key per device and revoke lost devices.
  • Keep keys and QR codes out of logs.
  • Restrict and encrypt configuration backups.
  • Expose only the UDP listener; no admin UI is required.
  • Patch the host and review the Docker security guide before adding other workloads.
  • Treat DNS and the VPS provider as separate trust decisions.

If you want a managed exit instead of server ownership, see the Proton VPN review. If you want repeatable peer generation and container rollback, continue with the WireGuard Docker Compose guide.

enim

Security researcher, CTF player, and compulsive self-hoster. Building byte-guard.net from a $10/mo Hetzner VPS. Everything I publish I have actually run in production.

Comments

Sign in with GitHub to comment. Threads live in the byteguard-comments repo.