Quick answer: install WireGuard, generate a key pair for the server and each client, write a server config that enables IP forwarding and a NAT masquerade rule, then bring the tunnel up:
apt update && apt install -y wireguard
wg genkey | tee server_private.key | wg pubkey > server_public.key
systemctl enable --now wg-quick@wg0
The single most common failure is a tunnel that connects but carries no traffic. That is almost always a missing IP forwarding setting or a missing masquerade rule, both covered below.
Step 1: install WireGuard
On a current Ubuntu or Debian VPS, WireGuard ships in the standard repositories:
apt update
apt install -y wireguard
This installs the wg and wg-quick tools. On modern kernels the WireGuard module is already built in, so there is nothing else to load.
Step 2: generate keys
Create a private and public key for the server. The umask 077 keeps the private key readable only by root:
cd /etc/wireguard
umask 077
wg genkey | tee server_private.key | wg pubkey > server_public.key
Generate a key pair for each client the same way (run this on the client, or generate on the server and hand the private key over securely):
wg genkey | tee client_private.key | wg pubkey > client_public.key
A WireGuard key pair is two matched files. The private key never leaves the machine it belongs to. Only public keys are exchanged between server and client.
Step 3: write the server config
Create /etc/wireguard/wg0.conf. Replace the key placeholders with the real contents of the files you generated, and replace eth0 with your VPS network interface if it differs (check with ip route show default):
[Interface]
Address = 10.8.0.1/24
ListenPort = 51820
PrivateKey = <server_private.key contents>
PostUp = iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
[Peer]
PublicKey = <client_public.key contents>
AllowedIPs = 10.8.0.2/32
The PostUp and PostDown lines add and remove the NAT masquerade rule automatically when the tunnel starts and stops. Without masquerade, packets from clients reach the server and then vanish, because the wider internet has no route back to the private 10.8.0.0/24 range.
Step 4: enable IP forwarding
By default a Linux server does not forward packets between interfaces. Turn it on permanently:
echo 'net.ipv4.ip_forward = 1' > /etc/sysctl.d/99-wireguard.conf
sysctl -p /etc/sysctl.d/99-wireguard.conf
If you also route IPv6 through the tunnel, add net.ipv6.conf.all.forwarding = 1 to the same file.
Step 5: add a client config
On the client, create a config that points at your server public IP and routes all traffic through the tunnel:
[Interface]
Address = 10.8.0.2/24
PrivateKey = <client_private.key contents>
DNS = 1.1.1.1
[Peer]
PublicKey = <server_public.key contents>
Endpoint = your-server-ip:51820
AllowedIPs = 0.0.0.0/0
PersistentKeepalive = 25
Setting AllowedIPs = 0.0.0.0/0 on the client sends all of its traffic over the tunnel. Use a narrower range if you only want to reach specific networks. PersistentKeepalive keeps the connection alive through NAT on the client side.
Step 6: bring the tunnel up
systemctl enable --now wg-quick@wg0
wg show
wg show lists peers and, once a client connects, a recent handshake and transfer counters. systemctl enable makes the tunnel start on boot.
Troubleshooting: connects but no traffic
This is the failure that generates the most support questions. Work through these in order:
- IP forwarding is off. Run
sysctl net.ipv4.ip_forward. If it returns0, step 4 did not take. Re-apply it and confirm it survives a reboot. - The masquerade rule is missing. Run
iptables -t nat -L POSTROUTING -n -vand look for a MASQUERADE line on your outbound interface. If it is absent, yourPostUpline has the wrong interface name or the tunnel was started before the rule existed. Restart withwg-quick down wg0 && wg-quick up wg0. - Wrong outbound interface. The interface in the
PostUprule must match the one with the default route. Confirm withip route show default; it is ofteneth0but can beens3,enp1s0or similar on cloud images. - A firewall is blocking UDP 51820. WireGuard uses UDP, not TCP. If your provider or cloud platform has a network firewall in front of the VPS, open inbound UDP on your
ListenPortthere as well as in any local firewall. A tunnel that never completes a handshake (no recent handshake inwg show) usually means the port is blocked. - DNS fails but IPs work. If you can ping
1.1.1.1through the tunnel but names do not resolve, the clientDNSline is missing or the resolver is unreachable.
Handshake never happens
If wg show never lists a handshake, the client is not reaching the server at all. Check that the server public IP and port in the client Endpoint are correct, that the server is actually listening (ss -ulnp | grep 51820), and that no network firewall sits in the path dropping the UDP packets.
A note on acceptable use
Running your own VPN server on a VPS is a fully supported use case, and the traffic that flows through it is treated like any other workload under our acceptable use policy.