A free VPN app is a stranger you invite to read your traffic. A WireGuard server you run yourself is the opposite: a tiny tunnel between your devices and one machine you control. No dashboard to learn, no subscription tier, no account that can be sold. If you can SSH into a VPS and edit one configuration file, you can build this in an afternoon, and it is fast enough that you will forget it is there.
WireGuard suits this job because the design is small: public-key authentication instead of passwords or certificate chains, and a configuration file that fits on one screen. If a peer cannot connect, the cause is almost always a key pasted on the wrong side, a closed UDP port, or a missing forwarding rule.
Before you start
You need a VPS with a public IPv4 address, a non-root user with sudo access, and SSH access that already works. Any entry-level VPS will do: WireGuard uses almost no CPU or memory. If you have not picked a server yet, read the VPS buying playbook first.
You also need one client device, a laptop or a phone, with the official WireGuard app installed on the phone. Keep the SSH session open until the tunnel is verified, and harden the server first: the SSH hardening audit playbook covers key-only login and automatic updates.
Step 1: Install WireGuard on the server
On Ubuntu or Debian, WireGuard support is already in the kernel, so you only need the tools package:
sudo apt update
sudo apt install -y wireguard wireguard-tools qrencode
wg --versionIf wg prints a version, you are ready. Then find the server's public interface name, because the NAT rule later needs it:
ip route show defaultThe name after dev is usually eth0, ens3, or enp1s0. Write it down and use your real name wherever this guide says eth0.
Step 2: Generate one key pair per device
WireGuard has no usernames or passwords. Each side proves itself with a key pair: the server configuration holds the server's private key plus each client's public key, while each client configuration holds that client's private key plus the server's public key. Never paste a private key into the other side's file.
Create the directory with strict permissions, then generate the server key pair:
sudo install -d -m 700 /etc/wireguard
sudo sh -c 'umask 077; wg genkey | tee /etc/wireguard/server.key | wg pubkey > /etc/wireguard/server.pub'Generate a pair for your first client the same way, naming it laptop. Repeat this step, with a fresh pair, for every additional device:
sudo sh -c 'umask 077; wg genkey | tee /etc/wireguard/laptop.key | wg pubkey > /etc/wireguard/laptop.pub'Never reuse one client's private key on two devices. Two peers sharing a key will fight over the same tunnel address, and the connection will flap in a way that looks like a network outage.
Show the keys you will need next:
sudo cat /etc/wireguard/server.pub /etc/wireguard/laptop.pub
sudo cat /etc/wireguard/server.key /etc/wireguard/laptop.keyPrivate keys stay on the machine that owns them. Do not paste them into a chat, a ticket, or a screenshot.
Step 3: Write the server configuration
Create /etc/wireguard/wg0.conf, for example with sudo nano /etc/wireguard/wg0.conf, replacing the placeholders with the real keys and your real public interface:
[Interface]
Address = 10.8.0.1/24
ListenPort = 51820
PrivateKey = <SERVER_PRIVATE_KEY>
PostUp = iptables -A FORWARD -i wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
[Peer]
PublicKey = <LAPTOP_PUBLIC_KEY>
AllowedIPs = 10.8.0.2/32Note three choices. The 10.8.0.0/24 range is the tunnel's private network; pick another range if it overlaps with your home or office network. AllowedIPs on the server is a whitelist: this peer may only send traffic from 10.8.0.2. The PostUp and PostDown lines are the NAT glue that lets tunnel traffic leave the server for the internet.
Lock the file down, enable forwarding persistently, and open the port, allowing SSH first so you cannot lock yourself out:
sudo chmod 600 /etc/wireguard/wg0.conf
echo "net.ipv4.ip_forward = 1" | sudo tee /etc/sysctl.d/99-wireguard.conf
sudo sysctl --system
sudo ufw allow OpenSSH
sudo ufw allow 51820/udp
sudo ufw enableIf your provider has a separate cloud firewall or security group, open UDP 51820 there too. A host rule alone will not help if the provider blocks the port upstream.
Step 4: Bring the tunnel up
Start the interface and make it survive reboots:
sudo systemctl enable --now wg-quick@wg0
sudo wg showYou should see interface wg0 listening on port 51820 with a peer entry for your laptop. The peer will show no handshake yet, which is normal before the laptop connects.
Step 5: Write the client configuration
Save this as wg0.conf on a laptop, or paste the same text into the WireGuard phone app. Replace the placeholders with the real keys and your server's public IP:
[Interface]
PrivateKey = <LAPTOP_PRIVATE_KEY>
Address = 10.8.0.2/24
DNS = 1.1.1.1
[Peer]
PublicKey = <SERVER_PUBLIC_KEY>
Endpoint = <YOUR_SERVER_IP>:51820
AllowedIPs = 0.0.0.0/0, ::/0
PersistentKeepalive = 25On the client, AllowedIPs = 0.0.0.0/0, ::/0 means full tunnel: all traffic goes through the VPS, which is what most people want on untrusted Wi-Fi. If you only want to reach services on the server itself, narrow it to 10.8.0.0/24.
PersistentKeepalive = 25 matters more than it looks. Almost every client sits behind NAT, and NAT mappings expire silently; a keepalive every 25 seconds keeps the mapping alive so the server can reply.
Connect from the laptop with sudo wg-quick up wg0. For a phone, generate a QR code on the server from the client file and scan it in the app:
qrencode -t ansiutf8 < laptop.confStep 6: Verify it actually works
A tunnel showing as connected is not proof. Verify three things, in order. First, the handshake is real: on the server, run sudo wg show again and look for a recent latest handshake timestamp. No handshake means packets are not reaching the server, usually a firewall or endpoint problem. Second, your public IP changed: on the client, curl ifconfig.me should return your VPS address, not your home or mobile address. Third, DNS works: curl -I https://example.com should return a response instead of hanging.
Then retest after switching networks if you can.
Troubleshooting the three classic failures
No handshake, ever. Check sudo ufw status, then the provider firewall, then confirm the client Endpoint matches the server's real public IP and port. A single wrong character in a public key produces exactly this symptom, with no helpful error message.
Connected, but no internet. Forwarding or NAT is missing. Confirm net.ipv4.ip_forward is 1, check sudo iptables -t nat -L POSTROUTING, and confirm the interface name matches ip route show default.
Works on one device, breaks on another. You reused a key or an address. Every device needs its own key pair, its own [Peer] block, and its own tunnel address (10.8.0.3, 10.8.0.4, and so on).
What this server is, and is not
Running your own WireGuard server removes the free-VPN middleman, but it does not make you anonymous. Your traffic now exits from your VPS, which is registered to you, and the provider can still see connection metadata. If your goal is anonymity rather than securing untrusted networks, a personal VPN is the wrong tool.
Within that limit, this setup is genuinely good: one protocol, one port, keys instead of passwords, and a configuration short enough to audit by eye. If you want to compare tunnels before committing, the WireGuard versus Xray tunnel comparison covers where WireGuard wins and where a protocol designed to blend in with normal web traffic fits better.
Keep the server updated and rotate a client's key pair if a device is lost: delete its [Peer] block, restart the interface, and generate a fresh pair for the replacement.
Sources
- WireGuard Quick Start, wireguard.com: installation, key generation, and the
wg-quickinterface format used here. - WireGuard man pages for
wg(8)andwg-quick(8):AllowedIPs,PersistentKeepalive, and interface behavior. - Ubuntu Server documentation on WireGuard: package availability and kernel support.


