Skip to main content
February 27, 2026

Setting Up a Persistent WireGuard VPN on a Linode VPS

7 min read
vpnsecuritylinuxvps
open in claude ↗ chatgpt ↗ grok ↗

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 /32 here, 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/0 routes 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 eth0 and wasted time debugging why MASQUERADE wasn't working. ip route show default tells you the real interface name in seconds.
  • Client address should be /32, not /24. The server owns the subnet. Giving the client /24 can 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 600 on every file containing a private key, including wg0.conf itself. 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