Run Your Own WireGuard VPN Server on Ubuntu

WireGuard's strength is that it does one thing and does it well. The configuration fits on an index card.

Run Your Own WireGuard VPN Server on Ubuntu
Photo by Daniel Jerez / Unsplash

Introduction

If you've read the IKEv2/strongSwan guide on this site, you already know the case for running your own VPN: no subscription, no logging policy to trust, no third party sitting between you and the internet. IKEv2 with strongSwan is solid and battle-tested. WireGuard is a different animal entirely.

WireGuard was written from scratch with one obsessive focus: simplicity. The reference implementation is roughly 4,000 lines of code. Compare that to OpenVPN's 100,000+. That's not a marketing number — it's a security posture. Less code means less surface area for vulnerabilities, and means the whole protocol is actually auditable by a person who isn't a full-time cryptographer. It was merged into the Linux kernel mainline in version 5.6, which means on any modern Ubuntu system, it's a first-class, kernel-native feature — not a third-party module you're hoping someone maintains.

Beyond the code elegance, WireGuard is fast. The kernel module routes packets without leaving kernel space, and the cryptography primitives it uses — ChaCha20, Poly1305, Curve25519 — are modern, well-understood, and hardware-accelerated on most chips made in the last decade. On commodity VPS hardware, you'll hit gigabit-level throughput without effort. The protocol is also stateless and handshake-quiet: WireGuard peers don't broadcast their existence, which makes it meaningfully harder to fingerprint compared to traditional VPN protocols.

This guide walks you through setting up a WireGuard VPN server on an Ubuntu VPS — the full setup, including dual-stack IPv4/IPv6 routing, NAT, kernel-level forwarding configuration, and peer provisioning.

What you'll end up with:

  • A WireGuard VPN server running on UDP port 51820
  • Full IPv4 and IPv6 tunnel support with NAT to the VPS's external interface
  • UFW-based firewall rules for clean routing
  • A working peer configuration you can import into any WireGuard client (iOS, Android, macOS, Linux, Windows)

Prerequisites:

  • A VPS running Ubuntu 20.04 or 22.04
  • Root or sudo access
  • A domain name or static IP for the server endpoint
  • Basic comfort with Linux command-line operations

A Note on Trade-offs

WireGuard is excellent at speed, simplicity, and minimal attack surface. It's less suitable for use cases requiring dynamic IP assignment at scale, enterprise feature sets, or protocol obfuscation. WireGuard traffic is identifiable as WireGuard — if you're trying to evade deep packet inspection in a restrictive network environment, you'll want to look at obfuscation wrappers or a different protocol entirely.

If you need IKEv2 for device management integration or certificate-based authentication in a corporate context, the IKEv2 guide still applies. Both can coexist on the same server on different ports. If you want the fastest, simplest path to owning your own exit node, read on.


Step 1: Install WireGuard

First, update your package list and install WireGuard:

sudo apt update
sudo apt install wireguard -y

What this does: Installs the wireguard package, which includes the kernel module (wireguard.ko) and the userspace tools wg and wg-quick. On Ubuntu 20.04 and later, WireGuard is included in the mainline kernel, so this pulls a clean, distribution-supported package — no third-party repositories required.


Step 2: Generate Your Server's Key Pair

WireGuard uses public-key cryptography. Each peer — including your server — has a private key and a mathematically derived public key. The private key never leaves the machine it lives on. The public key is what you share with clients so they can authenticate the server.

Generate the private key and write it to disk:

wg genkey | sudo tee /etc/wireguard/private.key

Lock down the file permissions immediately:

sudo chmod go= /etc/wireguard/private.key

Derive the public key from the private key:

sudo cat /etc/wireguard/private.key | wg pubkey | sudo tee /etc/wireguard/public.key

What this does: wg genkey generates a 32-byte random private key and outputs it as base64. Piping it to tee writes it to disk while also displaying it in the terminal. The chmod go= command removes all permissions from group and other users — only root can read it from this point forward. wg pubkey derives the corresponding Curve25519 public key from the private key. The public key is safe to share; the private key is not, under any circumstances.

You'll need the contents of /etc/wireguard/private.key in Step 5, and you'll hand out /etc/wireguard/public.key to peers in Step 7.


Step 3: Choose Your Private Address Space

WireGuard creates a virtual internal network. Since you're routing this over a VPS, you'll NAT that internal network out through the VPS's external interface. You need two address ranges: one IPv4 private range (RFC 1918) and one IPv6 ULA range.

For IPv4: Choose something outside the common consumer router defaults. If you use 192.168.1.0/24 or 10.0.0.0/24, you'll create routing conflicts with virtually every home network your clients connect from. A reasonable, collision-resistant choice is 10.93.219.0/24. Your server will take 10.93.219.1.

Ranges to avoid:

  • 192.168.0.0/24 and 192.168.1.0/24 — default for most consumer routers
  • 10.0.0.0/24 — common default for cloud environments
  • 10.128.128.0/24 and 10.255.255.0/24 — common VPN and overlay defaults
  • 172.16.0.0/24 — common default in corporate environments

For IPv6: WireGuard supports dual-stack natively. Use a Unique Local Address (ULA) — the IPv6 equivalent of private space. ULAs start with fd followed by a locally assigned prefix. A valid example: fd1:2:3:4::/64. Your server will take fd1:2:3:4::1. The point is simply to pick something different from whatever prefix your clients' home networks use.


Step 4: Enable IP Forwarding

Your VPS needs to forward packets — not just receive them. This is what makes the VPN actually route traffic to the internet, rather than creating a tunnel that goes nowhere.

Add the following to the end of /etc/sysctl.conf:

net.ipv4.ip_forward = 1
net.ipv4.ip_no_pmtu_disc = 1
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.all.send_redirects = 0
net.ipv6.conf.all.forwarding = 1

Apply the changes immediately without rebooting:

sudo sysctl -p

What each setting does:

  • net.ipv4.ip_forward = 1 — Tells the kernel to forward IPv4 packets between interfaces. Without this, traffic enters the WireGuard interface and stops dead.
  • net.ipv4.ip_no_pmtu_disc = 1 — Disables Path MTU Discovery, which can cause fragmentation issues with VPN-encapsulated traffic when certain firewalls block ICMP.
  • net.ipv4.conf.all.rp_filter = 1 — Enables reverse path filtering: the kernel verifies that the return path for an incoming packet matches what it would use to reach that source. This prevents a class of basic IP spoofing attacks.
  • net.ipv4.conf.all.accept_redirects = 0 and send_redirects = 0 — Disables ICMP redirect acceptance and sending. On a routing VPN server, you don't want outside parties rewriting your routing table.
  • net.ipv6.conf.all.forwarding = 1 — The IPv6 equivalent of ip_forward. Required for dual-stack tunnel routing.

Step 5: Create the WireGuard Interface Configuration

sudo nano /etc/wireguard/wg0.conf

Add the following content, substituting your actual values:

[Interface]
PrivateKey = PRIVATE_KEY
Address = 10.93.219.1/24
Address = fd1:2:3:4::1/64
ListenPort = 51820
SaveConfig = true

PostUp = ufw route allow in on wg0 out on eth0
PostUp = iptables -t nat -I POSTROUTING -o eth0 -j MASQUERADE
PostUp = ip6tables -t nat -I POSTROUTING -o eth0 -j MASQUERADE
PreDown = ufw route delete allow in on wg0 out on eth0
PreDown = iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
PreDown = ip6tables -t nat -D POSTROUTING -o eth0 -j MASQUERADE

Replace PRIVATE_KEY with the contents of /etc/wireguard/private.key. Replace the Address values with the ranges you chose in Step 3.

What each setting does:

  • PrivateKey — The server's private key. This is the only secret in the entire configuration. Treat it accordingly.
  • Address — The IP addresses assigned to the wg0 interface itself. Two Address lines are valid — one for IPv4, one for IPv6.
  • ListenPort — UDP port 51820 is the WireGuard default. You can change it, but you'll need to update your firewall rules to match.
  • SaveConfig = true — When the interface shuts down, WireGuard writes the current peer table back to this file. This means peers you add at runtime in Step 7 will survive reboots automatically.
  • PostUp commands — Shell commands run when the interface comes up. These do two things: tell UFW to allow routed traffic from wg0 to eth0, and configure NAT (MASQUERADE) so that WireGuard clients appear to the internet with the VPS's public IP address.
  • PreDown commands — The corresponding cleanup: remove the UFW route and tear down the NAT rules before the interface goes down.

Important: If your external interface is not eth0, you need to change it in all six PostUp/PreDown lines. Run ip link show to find the correct name. Common alternatives on VPS providers include ens3, ens18, enp1s0, or veth0. Using the wrong interface name here will result in a tunnel that connects but routes nothing.


Step 6: Open the Firewall and Enable WireGuard

Allow WireGuard traffic through UFW:

sudo ufw allow 51820/udp

Enable and start the WireGuard service:

sudo systemctl enable wg-quick@wg0
sudo systemctl start wg-quick@wg0

Verify it's running:

sudo systemctl status wg-quick@wg0

What this does: wg-quick is a helper script that reads wg0.conf, creates the WireGuard interface, assigns addresses, configures routing, and runs the PostUp commands. Enabling the systemd unit ensures the VPN restarts automatically after reboots. The status command should show Active: active (running). If you see an error, the two most common causes are a misconfigured interface name in the PostUp lines, or a malformed private key paste (check for trailing whitespace or newlines).


Step 7: Add a Peer

To connect a client, you first need to generate a key pair on the client side. The process is the same as Step 2. On a Linux client:

wg genkey | tee ~/client-private.key
cat ~/client-private.key | wg pubkey | tee ~/client-public.key

On mobile, the WireGuard app can generate keys directly and export a QR code. Once you have the client's public key and have assigned it an IP within your WireGuard subnet, register the peer on the server:

sudo wg set wg0 peer <CLIENT_PUBLIC_KEY> allowed-ips 10.93.219.2/32,fd1:2:3:4::2/128

What this does: Registers the client's public key with the server and defines which source IP addresses are valid for that key. The /32 and /128 are intentional — each peer gets a specific assigned address, not a range. Because SaveConfig = true is set, this peer will be written back to wg0.conf automatically. You can also trigger a manual save with sudo wg-quick save wg0.

The client-side configuration file looks like this:

[Interface]
PrivateKey = CLIENT_PRIVATE_KEY
Address = 10.93.219.2/32, fd1:2:3:4::2/128
DNS = 2606:4700:4700::1112, 2606:4700:4700::1002, 1.1.1.2, 1.0.0.2

[Peer]
PublicKey = SERVER_PUBLIC_KEY
AllowedIPs = ::/0, 0.0.0.0/0
Endpoint = your-server.example.com:51820

What each client setting does:

  • Address — The specific IPs assigned to this client's WireGuard interface. Match these to what you used in the wg set command on the server.
  • DNS — These are Cloudflare's malware-filtering resolvers. Without this line, DNS queries from the client may bypass the tunnel and leak to the local network resolver. Substitute your preferred resolver if you have one.
  • PublicKey — Your server's public key, from /etc/wireguard/public.key.
  • AllowedIPs = ::/0, 0.0.0.0/0 — Route all IPv4 and IPv6 traffic through the tunnel. If you only want split tunneling — sending specific subnets through the VPN while leaving other traffic on the local network — narrow this to the specific prefixes you want tunneled.
  • Endpoint — Your VPS's public hostname or IP, and the ListenPort. Use a hostname rather than an IP if your VPS IP might change.

If you're importing this on a mobile device, you can generate a QR code for the config file on Linux:

sudo apt install qrencode -y
qrencode -t ansiutf8 < client.conf

The WireGuard mobile apps can scan the QR code directly to import the configuration.


Closing Thoughts

WireGuard's strength is that it does one thing and does it well. The configuration fits on an index card. The codebase is auditable by a motivated individual. The cryptographic choices are conservative and modern. There's a reason it earned a place in the Linux kernel mainline.

The trade-off is real: WireGuard is not a full PKI, it doesn't have built-in key revocation infrastructure, and there's no native management plane for a large fleet of peers. For a personal exit node or a small team's infrastructure — which is what this guide covers — that simplicity is a feature, not a gap. Add a peer with one command, remove one with one command, done.

If you're already running the IKEv2/strongSwan setup from the previous guide, both services can coexist on the same server on different ports without conflict. They solve slightly different problems for slightly different clients, and there's no reason to pick just one.