Overview
I ran OpenVPN on a cheap VPS for years. It worked, but connecting took fifteen seconds, and the config file was four hundred lines of certificate chains I never fully understood. WireGuard connects in under a second, and the config is about fifteen lines. The tradeoff is that some of the "it just works" magic OpenVPN provides has to be set up by hand. Here's what that looks like.
Why WireGuard is different
WireGuard lives in the kernel on Linux (5.6+), which is why it's fast. It has no concept of a "connection" in the traditional sense — it's UDP packets with cryptographic routing. This is why it's silent by default: it only responds to correctly authenticated packets, so port scanners see nothing.
The downside of that silence is that if something is misconfigured, you get no error messages. Just nothing. Debugging means checking logs on both ends and confirming packets are arriving.
Installing on the server
sudo apt update
sudo apt install wireguard
Generate the server keypair:
cd /etc/wireguard
umask 077
wg genkey | tee server.key | wg pubkey > server.pub
cat server.key server.pub
umask 077 before generating keys is not optional. Without it, the private key file is world-readable, and you've just leaked your VPN identity.
The server config
Create /etc/wireguard/wg0.conf:
[Interface]
Address = 10.8.0.1/24
ListenPort = 51820
PrivateKey = <contents of server.key>
# Enable IP forwarding and NAT
PostUp = iptables -A FORWARD -i %i -j ACCEPT
PostUp = iptables -A FORWARD -o %i -j ACCEPT
PostUp = iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i %i -j ACCEPT
PostDown = iptables -D FORWARD -o %i -j ACCEPT
PostDown = iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
Replace eth0 with your actual outbound interface. On a modern Debian or Ubuntu VPS it might be ens3, enp1s0, or something cloud-provider-specific. Check with ip route | grep default before you guess.
Enable IP forwarding persistently, since the iptables rules won't help if the kernel drops forwarded packets:
echo "net.ipv4.ip_forward=1" | sudo tee /etc/sysctl.d/99-wireguard.conf
sudo sysctl --system
Adding a peer
On your laptop, generate a keypair the same way. Then append to the server config:
[Peer]
# laptop
PublicKey = <laptop pubkey>
AllowedIPs = 10.8.0.2/32
AllowedIPs does two jobs at once: it tells WireGuard which source IPs this peer is allowed to send from, and which destination IPs should be routed to it. For a simple client, 10.8.0.2/32 is right. If you want the peer to act as a gateway for a whole subnet, use that subnet instead.
Bring it up:
sudo systemctl enable --now wg-quick@wg0
sudo wg show
The client config
[Interface]
PrivateKey = <laptop private key>
Address = 10.8.0.2/32
DNS = 1.1.1.1
[Peer]
PublicKey = <server pubkey>
Endpoint = your.server.ip:51820
AllowedIPs = 0.0.0.0/0, ::/0
PersistentKeepalive = 25
Two things here people get wrong:
AllowedIPs = 0.0.0.0/0 routes all traffic through the VPN. If you only want to reach the server's private network, set this to the VPN subnet instead, like 10.8.0.0/24. Using 0.0.0.0/0 makes the VPN a full tunnel, which is what you want for "hide my traffic from hotel wifi" but not for "reach my home NAS."
PersistentKeepalive = 25 sends a keepalive every 25 seconds. Without it, the connection dies when you're behind a NAT that times out idle UDP flows — which is most of them after a minute or two.
Firewall
sudo ufw allow 51820/udp
sudo ufw allow OpenSSH
sudo ufw enable
Notice that 51820 is UDP, not TCP. WireGuard only speaks UDP. If you have a cloud provider with a separate security group layer (AWS, GCP, Oracle), open the port there too — the OS firewall isn't the only thing blocking.
Debugging when nothing works
WireGuard is quiet. Here's the sequence I use:
# On both ends: is the interface up and does it see handshakes?
sudo wg show
# Is traffic even arriving? On the server:
sudo tcpdump -i eth0 udp port 51820 -n
# Check routing on the client
ip route get 1.1.1.1
# Look at the kernel log for tunnel errors
sudo dmesg | tail -50
| Symptom | Likely cause |
|---|---|
| No handshake at all | UDP blocked by cloud firewall or provider |
| Handshake works, no traffic flows | Missing IP forwarding or NAT rules |
| Works from home, not from phone on mobile data | MTU — drop client MTU to 1280 |
| Connects then drops after a minute | PersistentKeepalive missing |
The MTU issue is worth knowing about before you hit it. Mobile carriers sometimes use a lower MTU, and WireGuard's default 1420 is too high. If SSH or HTTPS hangs but ping works, that's the tell.
Things I'd skip
There are a dozen WireGuard "easy setup" scripts floating around. Most of them work, most of them also install a web UI, a database, and forty dependencies on your VPS. The manual setup above is a one-time fifteen minutes and leaves you with something you actually understand. I'd rather have that.
If you're managing multiple devices and want the coordination handled for you, Tailscale is the commercial version of this whole idea. It's WireGuard under the hood with a control plane that handles key distribution and NAT traversal. Worth it if you have more than a handful of devices; overkill for one person and a VPS.
