I wanted a simple, always-on VPN that routes all my mobile traffic through a server I control. No third-party VPN providers, no trust issues, no subscription fees beyond the VPS itself. WireGuard on a $5/month Linode instance turned out to be the cleanest way to do it. This post walks through the full setup -- server configuration, client configuration, IPv6 leak prevention, and the things I got wrong along the way.
Why WireGuard
WireGuard is a lightweight VPN protocol built into the Linux kernel since 5.6. Compared to OpenVPN or IPSec, it has a smaller codebase, faster handshakes, and better performance on mobile connections. The configuration is a single file on each side, and the entire tunnel can be up and running in under 10 minutes.
The setup I'm targeting is straightforward: one Linode VPS as the server, one mobile device as the client, full-tunnel routing so all internet traffic goes through the VPS.
VPS Preparation
I'm using a Linode Nanode -- 1 CPU core, 1 GB RAM, 25 GB storage, Ubuntu 22.04. It costs $5/month and is more than enough for a personal VPN server.
After spinning up the instance, the first step is installing WireGuard and making sure iptables rules survive reboots:
sudo apt update && sudo apt upgrade -y
sudo apt install wireguard iptables-persistent -y
Next, enable IPv4 forwarding. Without this, the VPS won't forward packets from VPN clients to the internet -- your tunnel will connect but nothing will load:
sudo sysctl -w net.ipv4.ip_forward=1
echo "net.ipv4.ip_forward=1" | sudo tee -a /etc/sysctl.conf
sudo sysctl -p
Check that the value is set:
sysctl net.ipv4.ip_forward
# Should return: net.ipv4.ip_forward = 1
Generating Keys
WireGuard uses public/private keypairs, one for the server and one for each client. Generate these before writing any configuration files.
Server keypair:
umask 077
wg genkey | tee /etc/wireguard/server_private.key | wg pubkey > /etc/wireguard/server_public.key
chmod 600 /etc/wireguard/server_private.key
Client keypair (generate on the client device, or on the server and transfer securely):
wg genkey | tee client_private.key | wg pubkey > client_public.key
The umask 077 ensures the private key files are created with restrictive permissions. These keys are the entire security model -- anyone with the private key can impersonate that peer.
Server Configuration
Create /etc/wireguard/wg0.conf on the VPS. Before writing this file, you need to know which network interface your VPS uses for its public IP. On Linode this is commonly eth0, but it varies:
ip route show default
# Look for "dev eth0" or "dev ens3" or similar
Replace eth0 in the config below with whatever your VPS actually uses:
[Interface]
Address = 10.0.0.1/24
ListenPort = 51820
PrivateKey = <SERVER_PRIVATE_KEY>
MTU = 1380
PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -A FORWARD -o %i -j ACCEPT; iptables -t nat -A POSTROUTING -s 10.0.0.0/24 -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -D FORWARD -o %i -j ACCEPT; iptables -t nat -D POSTROUTING -s 10.0.0.0/24 -o eth0 -j MASQUERADE
[Peer]
# client1 - phone
PublicKey = <CLIENT_PUBLIC_KEY>
AllowedIPs = 10.0.0.2/32
PersistentKeepalive = 25
Lock down the config file since it contains the server's private key:
chmod 600 /etc/wireguard/wg0.conf
What each setting does:
| Setting | Purpose |
|---|---|
Address = 10.0.0.1/24 |
VPN subnet, server gets .1 |
ListenPort = 51820 |
UDP port WireGuard listens on |
MTU = 1380 |
Lower than default 1420 for mobile-friendly connections |
PostUp / PostDown |
Automatically adds/removes NAT and forwarding rules |
AllowedIPs = 10.0.0.2/32 |
This peer owns only one IP on the VPN |
PersistentKeepalive = 25 |
Keeps NAT mappings alive for mobile clients |
To add more clients later, append additional [Peer] blocks with the next available IP (10.0.0.3/32, 10.0.0.4/32, etc.) and the client's public key.
Firewall
The PostUp rules handle forwarding and NAT, but you also need to allow WireGuard's UDP port through the host firewall. If you're using ufw:
sudo ufw allow 51820/udp
sudo ufw allow OpenSSH
sudo ufw enable
If you're relying on Linode's Cloud Firewall, add an inbound rule allowing UDP 51820. I initially disabled the Cloud Firewall for testing -- don't leave it that way. Re-enable it and allow only UDP 51820 and SSH.
Starting WireGuard
Bring the interface up and enable it on boot:
sudo wg-quick up wg0
sudo systemctl enable wg-quick@wg0
Verify it's running:
sudo wg
You should see the interface, listening port, and your peer listed with its public key.
Client Configuration
On the mobile device (or any client), create this configuration in the WireGuard app:
[Interface]
Address = 10.0.0.2/32
PrivateKey = <CLIENT_PRIVATE_KEY>
DNS = 1.1.1.1
[Peer]
PublicKey = <SERVER_PUBLIC_KEY>
Endpoint = VPS_PUBLIC_IP:51820
AllowedIPs = 0.0.0.0/0
PersistentKeepalive = 25
Key details:
- Address = 10.0.0.2/32 -- the client's specific VPN IP. Use
/32here, not/24. The server owns the subnet; the client owns a single address. - DNS = 1.1.1.1 -- required for full-tunnel VPN. When
AllowedIPs = 0.0.0.0/0routes all traffic through the tunnel, the client's original DNS server becomes unreachable. Without specifying DNS here, resolution breaks. - AllowedIPs = 0.0.0.0/0 -- routes all internet traffic through the VPN.
- Endpoint -- your VPS public IPv4 address and WireGuard port.
IPv6 Leak Prevention
If you're only running an IPv4 tunnel, IPv6 traffic bypasses the VPN entirely. This leaks your real IP on any site that supports IPv6. There are two approaches to fix this.
Option 1: Disable IPv6 on the VPS entirely
sudo sysctl -w net.ipv6.conf.all.disable_ipv6=1
sudo sysctl -w net.ipv6.conf.default.disable_ipv6=1
sudo sysctl -w net.ipv6.conf.lo.disable_ipv6=1
To make it permanent, add these lines to /etc/sysctl.conf (check they don't already exist before appending):
net.ipv6.conf.all.disable_ipv6 = 1
net.ipv6.conf.default.disable_ipv6 = 1
net.ipv6.conf.lo.disable_ipv6 = 1
Then reload:
sudo sysctl -p
Option 2: Drop all IPv6 traffic via ip6tables
sudo ip6tables -P INPUT DROP
sudo ip6tables -P FORWARD DROP
sudo ip6tables -P OUTPUT DROP
sudo netfilter-persistent save
I went with Option 1 since I have no use for IPv6 on this VPS.
Verifying the Setup
On the VPS:
ip addr show wg0 # Interface should be up, MTU 1380
sudo wg # Peer handshake should be recent
sudo iptables -t nat -L -v -n # MASQUERADE rule for 10.0.0.0/24
sudo iptables -L FORWARD -v -n # ACCEPT rules for wg0
On the client:
ping 10.0.0.1 # VPN server internal IP
ping 8.8.8.8 # Internet connectivity
curl https://ifconfig.me # Should return VPS public IPv4
If the handshake completes but internet doesn't work, the most common causes are: IP forwarding not enabled, MASQUERADE rule referencing the wrong interface name, or the host firewall blocking UDP 51820.
Persisting iptables Rules
The PostUp and PostDown directives in wg0.conf handle the NAT and forwarding rules automatically when WireGuard starts and stops. However, if you added separate rules (like the IPv6 drop rules), save them explicitly:
sudo netfilter-persistent save
This ensures those rules survive a reboot.
What I Learned
- Check your interface name. I assumed
eth0and wasted time debugging why MASQUERADE wasn't working.ip route show defaulttells you the real interface name in seconds. - Client address should be /32, not /24. The server owns the subnet. Giving the client
/24can cause routing conflicts where traffic meant for the internet gets routed locally. - DNS is not optional on full-tunnel VPNs. When all traffic routes through the tunnel, the client loses access to its original DNS server. Always set a DNS server in the client config.
- Lock down your private keys.
chmod 600on every file containing a private key, includingwg0.confitself. WireGuard's entire trust model is the keypair. - MTU 1380 avoids mobile headaches. The default 1420 causes fragmentation issues on some cellular networks. Dropping to 1380 fixed intermittent connection drops on my phone.
- WireGuard is remarkably simple. The entire server config is one file, one systemd service, and a few iptables rules. After years of fighting OpenVPN configs, this felt like cheating.
Research Resources
- WireGuard Official Documentation - Protocol overview and quickstart
- Linode WireGuard Guide - Platform-specific setup steps
- iptables NAT Tutorial - Understanding MASQUERADE and POSTROUTING
- WireGuard on Mobile - Client apps for iOS and Android