Why WireGuard?
If you’ve ever needed to access your home network while traveling, secure your connection on public Wi-Fi, or just tunnel traffic through your own server, a VPN is the answer. For years, OpenVPN was the go-to. But WireGuard changed everything when it was merged into the Linux kernel in 2019.
WireGuard is:
- Fast. It uses state-of-the-art cryptography (Noise protocol framework, Curve25519, ChaCha20) and runs inside the kernel, so it’s noticeably snappier than OpenVPN.
- Simple. The entire codebase is ~4,000 lines of code versus OpenVPN’s ~100,000. Fewer lines means fewer bugs and easier auditing.
- Modern. Built into the Linux kernel (5.6+), with first-class support on Linux, macOS, Windows, iOS, and Android.
In this tutorial, we’ll set up a WireGuard VPN server on Ubuntu, configure a client, and test the whole thing end to end. By the finish, you’ll have your own private tunnel that’s faster and leaner than anything commercial VPNs offer.
Prerequisites
- An Ubuntu 22.04+ server (VPS or bare metal) with a public IP address
- Root or sudo access
- ufw (Uncomplicated Firewall) installed (default on Ubuntu)
- A client device (laptop, phone, or another machine) to connect from
- Basic familiarity with the command line
Part 1: Install WireGuard on the Server
First, update your system and install WireGuard:
sudo apt update && sudo apt upgrade -y
sudo apt install wireguard -y
That’s it for installation. WireGuard ships as a kernel module (on modern kernels) plus a lightweight user-space tool called wg.
Verify it’s installed:
wg --version
Part 2: Generate Keys
WireGuard uses public-key cryptography. Each side of the tunnel needs a private key (kept secret) and a public key (shared with the other side).
On the server, generate both keys:
# Generate private key
wg genkey | sudo tee /etc/wireguard/server_private.key
sudo chmod 600 /etc/wireguard/server_private.key
# Derive public key from the private key
sudo cat /etc/wireguard/server_private.key | wg pubkey | sudo tee /etc/wireguard/server_public.key
Save both values somewhere safe for a moment. You’ll need the public key when configuring clients.
Do the same on your client machine:
# On the client
wg genkey | tee client_private.key
chmod 600 client_private.key
cat client_private.key | wg pubkey > client_public.key
Part 3: Configure the Server
Create the server configuration file:
sudo nano /etc/wireguard/wg0.conf
Paste the following (adjust values marked with <ANGLE BRACKETS>):
[Interface]
Address = 10.0.0.1/24
ListenPort = 51820
PrivateKey = <SERVER_PRIVATE_KEY>
# Client: your laptop (example)
[Peer]
PublicKey = <CLIENT_PUBLIC_KEY>
AllowedIPs = 10.0.0.2/32
Let’s break this down:
- Address: The VPN-internal IP address of this server. We’re using the
10.0.0.0/24subnet — a private range reserved for this tunnel. - ListenPort:
51820is WireGuard’s default UDP port. Change it if you want, but 51820 is fine for most setups. - PrivateKey: The server’s private key (never share this).
- AllowedIPs (under [Peer]):
10.0.0.2/32means “only allow traffic claiming to be from 10.0.0.2.” The /32 is a single IP — a security best practice so clients can’t impersonate each other.
Part 4: Enable IP Forwarding and Firewall Rules
If you want your client to route all internet traffic through the VPN server (like a commercial VPN), you need to enable IP forwarding and add NAT masquerading. If you just want to access your home network, you can skip the NAT part.
Enable IP forwarding (persistent across reboots):
echo "net.ipv4.ip_forward = 1" | sudo tee /etc/sysctl.d/99-wireguard.conf
sudo sysctl -p /etc/sysctl.d/99-wireguard.conf
Find your main network interface:
ip route | grep default
# Usually outputs something like: default via 192.168.1.1 dev eth0 proto dhcp
# The interface name is 'eth0', 'ens3', 'enp0s3', etc.
Let’s call that interface eth0 for this example.
Add NAT masquerading (so VPN clients can reach the internet through the server):
# Add iptables rule
sudo iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
# Make it persistent across reboots
sudo apt install iptables-persistent -y
sudo netfilter-persistent save
Open the WireGuard port in ufw:
sudo ufw allow 51820/udp
sudo ufw allow OpenSSH
sudo ufw enable
Check the status:
sudo ufw status verbose
You should see 51820/udp ALLOW in the output.
Part 5: Start WireGuard
Bring up the WireGuard interface:
sudo wg-quick up wg0
You should see output like:
[#] ip link add wg0 type wireguard
[#] wg setconf wg0 /dev/fd/63
[#] ip -4 address add 10.0.0.1/24 dev wg0
[#] ip link set mtu 1420 up dev wg0
Enable it to start on boot:
sudo systemctl enable wg-quick@wg0
Check the status to verify it’s running and see what peers are connected:
sudo wg show
Right now you should see your server’s public key and the peer you configured, but latest handshake will be empty (because the client isn’t connected yet).
Part 6: Configure the Client
On your client machine, create the client configuration:
nano client-wg0.conf
Paste:
[Interface]
PrivateKey = <CLIENT_PRIVATE_KEY>
Address = 10.0.0.2/24
DNS = 1.1.1.1, 8.8.8.8
[Peer]
PublicKey = <SERVER_PUBLIC_KEY>
AllowedIPs = 0.0.0.0/0
Endpoint = <YOUR_SERVER_PUBLIC_IP>:51820
PersistentKeepalive = 25
Let’s talk about AllowedIPs and PersistentKeepalive:
- AllowedIPs = 0.0.0.0/0 means “route ALL traffic through the VPN.” If you only want to reach your home network, use AllowedIPs = 10.0.0.0/24 (or whatever your LAN subnet is). The
0.0.0.0/0option turns this into a full-tunnel VPN — your server becomes your exit node to the internet. - PersistentKeepalive = 25 sends a keepalive packet every 25 seconds. This is crucial if either side is behind NAT (which almost everyone is). Without it, the connection can go idle and break.
- Endpoint is the public IP (or domain name) and port of your WireGuard server.
Now import and connect:
On Linux:
sudo cp client-wg0.conf /etc/wireguard/wg0.conf
sudo wg-quick up wg0
On macOS / iOS / Android: Install the WireGuard app from the App Store / Play Store, then scan the QR code or import the config file. To generate a QR code from the terminal:
# Install qrencode first: sudo apt install qrencode
qrencode -t ansiutf8 < client-wg0.conf
On Windows: Install the WireGuard client from wireguard.com/install, then import the .conf file and activate the tunnel.
Part 7: Verify the Connection
On the server:
sudo wg show
You should now see a latest handshake timestamp (within the last few seconds) and transfer bytes changing as traffic flows.
On the client, test the tunnel:
# Ping the server through the VPN
ping 10.0.0.1
# Check your public IP (if using full tunnel)
curl ifconfig.me
If you set AllowedIPs = 0.0.0.0/0, curl ifconfig.me should return your server’s public IP, not your normal ISP address. That means all your traffic is going through the VPN. Nice.
Adding More Clients
Every new client needs their own key pair and a unique VPN IP. Here’s the workflow:
- Generate keys on the new client
- Add a
[Peer]block to the server’s/etc/wireguard/wg0.conf - Create the client config with the new client’s keys
- Apply the changes
Add a new peer to the server config:
sudo nano /etc/wireguard/wg0.conf
Append:
# Client: phone
[Peer]
PublicKey = <PHONE_PUBLIC_KEY>
AllowedIPs = 10.0.0.3/32
Apply without dropping existing connections:
sudo wg syncconf wg0 <(wg-quick strip wg0)
This reloads the configuration live — no need to disconnect existing peers. It’s like nginx -s reload for WireGuard.
Common Pitfalls and Troubleshooting
❌ “No handshake completed”
The most common issue. Check:
- The port 51820/udp is open on your firewall (both ufw on the server and your hosting provider’s security group if applicable)
- The
Endpointin the client config points to the correct public IP - The keys match (client’s public key in server config, server’s public key in client config)
- You’re not accidentally using TCP — WireGuard uses UDP only
❌ Connected but no internet access
- Check IP forwarding:
cat /proc/sys/net/ipv4/ip_forwardshould return1 - Check NAT masquerading:
sudo iptables -t nat -L POSTROUTING -vshould show the MASQUERADE rule - Make sure
AllowedIPs = 0.0.0.0/0is set on the client if you want full-tunnel
❌ Slow speeds
- Run a speed test:
iperf3 -s(server) theniperf3 -c 10.0.0.1(client) to isolate VPN throughput from internet speed - Try changing the MTU in the client config: add
MTU = 1280under[Interface] - If on a VPS, check that your provider isn’t rate-limiting UDP traffic
❌ WireGuard drops after a few minutes of inactivity
Add PersistentKeepalive = 25 under the client’s [Peer] section. This keeps NAT mappings alive on both ends.
Bonus: Split Tunneling vs. Full Tunnel
A quick decision matrix:
| Goal | Client AllowedIPs setting |
|---|---|
| Access home network only | 10.0.0.0/24, 192.168.1.0/24 (VPN + LAN subnets) |
| Browse via your server IP | 0.0.0.0/0 (all IPv4 traffic) |
| Browse IPv6 too | 0.0.0.0/0, ::/0 (dual-stack full tunnel) |
Split tunneling (AllowedIPs limited to specific subnets) is lightweight — you only route what needs to go through the VPN. Full tunnel (0.0.0.0/0) replaces your ISP exit point with your server, which is great for privacy but means your server’s upload bandwidth becomes your bottleneck.
What Makes WireGuard Different
Let me be honest about why I reached for WireGuard instead of OpenVPN:
- One command to connect. No certificate authorities, no TLS handshakes, no PKI. Public keys are the identity. Period.
- Roaming support built in. Switch from Wi-Fi to cellular on your phone? The tunnel survives because WireGuard doesn’t care about the underlying transport IP — it authenticates by key.
- Cryptographically opinionated. There are no cipher suites to negotiate. You get the right crypto every time. No chance of accidentally configuring weak ciphers.
The “KISS” principle fully applies here: Keep It Simple and Secure.
Wrapping Up
You now have a WireGuard VPN server running on Ubuntu, a client that can connect to it, and the knowledge to add more clients. The entire setup is a single config file, a handful of iptables rules, and about 30 minutes of work.
From here, you could:
- Deploy Pi-hole (we’ve covered that) on the same server and point your client DNS to it for network-wide ad blocking through the VPN
- Set up automatic SSL on Nginx to serve services behind the VPN
- Automate backups of your WireGuard configs with systemd timers
Own your traffic. Own your data. Own your corner of the internet.
Leave a Reply