Skip to content

guide

A WireGuard tunnel to your London instance

WireGuard is a VPN that lives in the kernel, uses one UDP port, and fits in about fifteen lines of config per side. Use it to reach private services on your instance without exposing them, or to give a laptop a stable UK exit address.

Plan the addresses

Pick a private subnet that does not collide with your home or office network. 10.66.0.0/24 is unlikely to clash. The server takes 10.66.0.1; each client gets the next address. Pick a UDP port; 51820 is the convention.

A Spark instance handles a small team comfortably. WireGuard's cost is CPU for encryption, and one vCPU moves a lot of traffic with ChaCha20.

Server: install and generate keys

sudo apt install -y wireguard
# AlmaLinux / Rocky 9: sudo dnf install -y wireguard-tools
sudo -i
cd /etc/wireguard
umask 077
wg genkey | tee server.key | wg pubkey > server.pub
wg genkey | tee laptop.key | wg pubkey > laptop.pub
exit

umask 077 makes sure the private keys are readable only by root. Generating the client key on the server is convenient for a first setup; for long-lived devices, generate it on the device and only copy the public key across.

Server: /etc/wireguard/wg0.conf

[Interface]
Address = 10.66.0.1/24
ListenPort = 51820
PrivateKey = <contents of server.key>

[Peer]
# laptop
PublicKey = <contents of laptop.pub>
AllowedIPs = 10.66.0.2/32

On the server side, AllowedIPs is a routing rule and an access filter at once: packets from this peer are only accepted if their source is inside that range, and packets for that range are sent to this peer. Use a /32 per client.

Forwarding and NAT (only for full-tunnel clients)

If clients only need to reach services on the instance itself, skip this section. If they should send all internet traffic through London, the server must forward and masquerade.

echo 'net.ipv4.ip_forward=1' | sudo tee /etc/sysctl.d/99-wireguard.conf
sudo sysctl --system

Add a NAT table and a forward rule to your nftables ruleset. Find your public interface name with ip -br link; it is often eth0 or ens18.

table ip nat {
  chain postrouting {
    type nat hook postrouting priority 100;
    oifname "eth0" ip saddr 10.66.0.0/24 masquerade
  }
}

# inside table inet filter, chain forward:
iifname "wg0" oifname "eth0" accept
iifname "eth0" oifname "wg0" ct state established,related accept

Also make sure udp dport 51820 accept is in the input chain, and allow the same UDP port in the portal firewall.

Start it

sudo systemctl enable --now wg-quick@wg0
sudo wg show

You should see the interface, its public key, the listening port, and one peer with no handshake yet.

Client config

[Interface]
PrivateKey = <contents of laptop.key>
Address = 10.66.0.2/32
DNS = 1.1.1.1

[Peer]
PublicKey = <contents of server.pub>
Endpoint = 203.0.113.10:51820
AllowedIPs = 10.66.0.0/24
PersistentKeepalive = 25

This is a split tunnel: only traffic for 10.66.0.0/24 uses the VPN. For a full tunnel, set AllowedIPs = 0.0.0.0/0 and make sure the NAT section above is in place. Import the file into the WireGuard app on macOS, Windows, iOS or Android, or on Linux save it as /etc/wireguard/wg0.conf and run wg-quick up wg0. For phones, qrencode -t ansiutf8 < laptop.conf prints a QR code the app can scan.

Test

ping -c3 10.66.0.1         # from the client
sudo wg show               # on the server: "latest handshake: 4 seconds ago"
curl -4 ifconfig.co        # full tunnel only: should print 203.0.113.10

Now hide the private services

The point of the tunnel is usually what you stop exposing. Bind admin panels, database ports and metrics endpoints to 10.66.0.1 instead of 0.0.0.0, and restrict SSH to the tunnel:

iifname "wg0" tcp dport 22 accept

Keep the public SSH rule until you have confirmed you can log in over the tunnel, then remove it. The browser console in the portal remains your way in if the tunnel ever breaks.

Adding another peer

sudo wg set wg0 peer <new-client-pubkey> allowed-ips 10.66.0.3/32
sudo wg-quick save wg0

wg set applies immediately without dropping other peers. wg-quick save writes the live state back to the config file so it survives a reboot. Revoking a device is the reverse: wg set wg0 peer <pubkey> remove.

Pitfalls

  • Swapping keys: each side's config holds its own private key and the other side's public key.
  • MTU trouble on some mobile networks shows as tunnels where ping works but web pages stall. Set MTU = 1380 in the client's [Interface].
  • Overlapping subnets: if your office uses 10.66.0.0/24 already, routes conflict silently. Choose another range.

Questions

Why does wg show say no handshake?

WireGuard is silent until traffic flows. Ping the server's tunnel address from the client to trigger a handshake. If it still never appears, the UDP port is blocked, the endpoint address is wrong, or the public keys are swapped between the two configs.

Can several clients share one config file?

No. Each peer needs its own key pair and its own tunnel address. Sharing a key means two devices fight over the same peer entry and the connection flaps between them.

Does WireGuard work behind a hotel or office NAT?

Yes, as long as outbound UDP to your chosen port is allowed. Set PersistentKeepalive = 25 on the client so the NAT mapping stays open while the tunnel is idle.

Is this the same as a commercial VPN service?

Technically similar, but every packet exits from your instance's single IPv4 address, which is tied to your account. It suits reaching your own infrastructure and having a predictable UK address, not anonymity.