WireGuard can give a laptop or workstation a private route to an Ubuntu 24.04 server without turning that server into a full-tunnel internet gateway. In this tutorial, you will install WireGuard, create a 10.66.66.0/24 tunnel, add one client peer, keep normal client internet traffic outside the VPN, protect the UDP listener, verify the route and handshake, and test private SSH access before changing any public administration path.
Raff Technologies is the VM platform used by the saved test environment. The existing test note remains Ubuntu 24.04 LTS on Raff 1 vCPU / 2 GB RAM VM; Linux client for tunnel verification. This revision updates the operational guidance against current Ubuntu Server and WireGuard documentation without claiming a new end-to-end test.
This is a split-tunnel design. The client uses AllowedIPs = 10.66.66.0/24, so only traffic for the WireGuard subnet enters the tunnel. There is no IPv4 forwarding, NAT, or masquerading because the goal is private access to services on the WireGuard server itself. If you want all internet traffic to exit through the VM, use the separate full-tunnel tutorial.
WireGuard's AllowedIPs setting is especially important: on transmit it selects which peer should receive traffic for a destination; on receive it also restricts which source addresses that peer is allowed to use. Ubuntu's current WireGuard documentation describes those routing and access-control roles explicitly.
Prerequisites:
- Ubuntu 24.04 with SSH and sudo access
- A public IPv4 address or reachable DNS name for the WireGuard endpoint
- A client with WireGuard support
- UDP port
51820reachable through the firewall protecting the VM - A VPN subnet that does not overlap the client or server networks
Step 1 — Verify Ubuntu and choose a non-overlapping VPN subnet
Confirm the server release and current routes:
cat /etc/os-release ip -4 addr ip -4 route
This tutorial uses:
WireGuard interface: wg0 VPN subnet: 10.66.66.0/24 Server VPN address: 10.66.66.1/24 Client VPN address: 10.66.66.2/32 UDP listener: 51820
Check whether the proposed subnet already appears in the server routing table:
ip -4 route | grep '10\.66\.66\.0/24' || true
Run an equivalent route check on the client. If either side already uses 10.66.66.0/24, choose a different RFC1918 subnet and use it consistently throughout the configuration.
Verify: Ubuntu should report 24.04, the public endpoint should be known, and 10.66.66.0/24 should not conflict with an existing route on either peer.
Step 2 — Install WireGuard from Ubuntu's package repository
Install WireGuard:
sudo apt update sudo apt install -y wireguard
The official WireGuard installation page uses sudo apt install wireguard for Ubuntu, and the standard Ubuntu kernel includes WireGuard support.
Verify the userspace tools:
wg --version wg-quick --help 2>&1 | head
Load the kernel module explicitly as a diagnostic check:
sudo modprobe wireguard lsmod | grep '^wireguard' || true
A missing lsmod line after normal use is not by itself proof of failure on every kernel configuration; the functional checks later in the tutorial are what matter.
Verify: wg --version and wg-quick should run successfully without installing a third-party VPN package or repository.
Step 3 — Generate the server key pair without exposing the private key
Create the WireGuard directory and a root-only server key:
sudo install -d -m 700 /etc/wireguard sudo sh -c 'umask 077; wg genkey > /etc/wireguard/wg0.key' sudo sh -c 'wg pubkey < /etc/wireguard/wg0.key > /etc/wireguard/wg0.pub'
Set explicit permissions:
sudo chown root:root /etc/wireguard/wg0.key /etc/wireguard/wg0.pub sudo chmod 600 /etc/wireguard/wg0.key sudo chmod 644 /etc/wireguard/wg0.pub
Display only the public key:
sudo cat /etc/wireguard/wg0.pub
WireGuard's official quick start recommends creating private keys under a restrictive umask. Ubuntu's security guidance also recommends keeping private keys out of configuration text that might later be shared for troubleshooting.
Verify: /etc/wireguard/wg0.key should be mode 600; the public key may be displayed or copied, but the private key should never be printed, pasted into tickets, or committed to source control.
Step 4 — Create the server configuration for private-only access
Create /etc/wireguard/wg0.conf without embedding the server private key:
sudo tee /etc/wireguard/wg0.conf >/dev/null <<'EOF' [Interface] Address = 10.66.66.1/24 ListenPort = 51820 PostUp = wg set %i private-key /etc/wireguard/%i.key SaveConfig = false EOF sudo chown root:root /etc/wireguard/wg0.conf sudo chmod 600 /etc/wireguard/wg0.conf
This server does not need:
net.ipv4.ip_forward = 1 MASQUERADE / NAT rules AllowedIPs = 0.0.0.0/0
Those belong to routed or full-tunnel designs. Here, the client only needs to reach 10.66.66.1 and any service intentionally listening on that server address.
Inspect only the non-secret configuration:
sudo cat /etc/wireguard/wg0.conf
Because the private key is loaded from /etc/wireguard/wg0.key, this output does not expose it.
Verify: The interface should use 10.66.66.1/24, listen on UDP 51820, load its private key from the protected key file, and contain no forwarding or NAT rule.
Step 5 — Allow the WireGuard UDP listener without risking SSH lockout
Check UFW state first:
sudo ufw status numbered
If UFW is already active, allow WireGuard:
sudo ufw allow 51820/udp comment 'WireGuard VPN' sudo ufw status numbered
If UFW is inactive, do not blindly enable it from a single remote SSH session. First follow the lockout-safe process in Set Up UFW Firewall on Ubuntu 24.04, verify your real SSH port is allowed, and keep a second SSH session open while testing.
If another firewall protects the VM outside Ubuntu, allow UDP 51820 there as well. WireGuard does not use TCP 51820 in this configuration.
Verify: UDP 51820 should be reachable through every firewall layer that protects the VM, while the existing SSH administration path should remain working.
Step 6 — Generate the client key pair on the client device
Generate client private keys on the client whenever possible so the private key never has to travel through the server.
On an Ubuntu or Debian client:
sudo apt update sudo apt install -y wireguard
Create a protected directory and client keys:
mkdir -p ~/.config/wireguard-raff chmod 700 ~/.config/wireguard-raff umask 077 wg genkey > ~/.config/wireguard-raff/client.key wg pubkey < ~/.config/wireguard-raff/client.key \ > ~/.config/wireguard-raff/client.pub
Display only the client public key:
cat ~/.config/wireguard-raff/client.pub
Check the private-key permissions:
stat -c '%a %n' ~/.config/wireguard-raff/client.key
Windows, macOS, iOS, and Android users can generate the peer keys in the official WireGuard application and use the same tunnel addresses shown in this guide.
Verify: The client private key should remain on the client with restrictive permissions; only the client public key should be copied to the server.
Step 7 — Add the client peer and understand AllowedIPs
On the server, paste the client public key into a temporary shell variable:
CLIENT_PUBLIC_KEY='PASTE_CLIENT_PUBLIC_KEY_HERE'
Append the peer:
sudo tee -a /etc/wireguard/wg0.conf >/dev/null <<EOF [Peer] PublicKey = ${CLIENT_PUBLIC_KEY} AllowedIPs = 10.66.66.2/32 EOF unset CLIENT_PUBLIC_KEY
On the server, AllowedIPs = 10.66.66.2/32 means two things:
- traffic destined for
10.66.66.2is associated with this peer; - packets received from this peer are accepted as coming from
10.66.66.2, not arbitrary tunnel addresses.
Do not assign the same tunnel address to multiple peers.
Inspect the peer block without exposing the server private key:
sudo grep -A2 '^\[Peer\]' /etc/wireguard/wg0.conf
Verify: Exactly one peer should own 10.66.66.2/32, and the placeholder public key must be replaced before starting the interface.
Step 8 — Start WireGuard and enable reboot persistence
Start the interface through systemd:
sudo systemctl enable --now wg-quick@wg0
Check service state:
systemctl is-active wg-quick@wg0 systemctl is-enabled wg-quick@wg0
Inspect the tunnel address and runtime WireGuard state:
ip -brief addr show wg0 sudo wg show wg0 sudo ss -lunp | grep ':51820' || true
Before the client sends traffic, latest handshake may be absent. WireGuard intentionally stays quiet when there is nothing to send.
Verify: wg0 should own 10.66.66.1/24, systemd should report the service active/enabled, and WireGuard should listen on UDP 51820 with the client peer loaded.
Step 9 — Create the split-tunnel client configuration
Get the server public key:
sudo cat /etc/wireguard/wg0.pub
On the client, load its private key into a temporary variable:
CLIENT_PRIVATE_KEY="$(cat ~/.config/wireguard-raff/client.key)"
Create /etc/wireguard/raffvpn.conf, replacing SERVER_PUBLIC_KEY and SERVER_PUBLIC_IP_OR_DNS:
sudo tee /etc/wireguard/raffvpn.conf >/dev/null <<EOF [Interface] PrivateKey = ${CLIENT_PRIVATE_KEY} Address = 10.66.66.2/32 [Peer] PublicKey = SERVER_PUBLIC_KEY Endpoint = SERVER_PUBLIC_IP_OR_DNS:51820 AllowedIPs = 10.66.66.0/24 PersistentKeepalive = 25 EOF unset CLIENT_PRIVATE_KEY sudo chmod 600 /etc/wireguard/raffvpn.conf
AllowedIPs = 10.66.66.0/24 is the split-tunnel rule: only that private subnet is routed through WireGuard. It does not replace the client's normal default route.
PersistentKeepalive = 25 is useful when the client is behind NAT or a stateful firewall and needs the mapping to stay usable while idle. WireGuard documents 25 seconds as a sensible interval for that case. If the client does not need it, omit the setting rather than enabling unnecessary keepalives.
Verify: The client should use 10.66.66.2/32, point to the server's real public endpoint, route only 10.66.66.0/24 through the peer, and have a mode-600 configuration file.
Step 10 — Bring up the client and verify the split route
Start the client interface:
sudo wg-quick up raffvpn
Inspect its address and WireGuard state:
ip -brief addr show raffvpn sudo wg show raffvpn
Verify the route to the private server address:
ip route get 10.66.66.1
It should use the WireGuard interface.
Then verify that an ordinary internet destination still follows the normal client route:
ip route get 1.1.1.1
That route should not be replaced by this WireGuard tunnel.
Verify: 10.66.66.1 should route through raffvpn, while a normal internet destination should continue to use the client's pre-existing default route.
Step 11 — Verify a real handshake and bidirectional tunnel traffic
Trigger the tunnel from the client:
ping -c 4 10.66.66.1
Inspect the client:
sudo wg show raffvpn
Inspect the server:
sudo wg show wg0
A working tunnel should show a recent handshake and increasing transfer counters on both sides.
You can also watch the session live on the server while sending client traffic:
watch -n 1 sudo wg show wg0
Ubuntu's troubleshooting guidance specifically recommends checking keys, AllowedIPs, routes, interface addresses, and wg state rather than treating an interface that is merely “up” as proof of connectivity.
