- What Is WireGuard?
- How WireGuard Works
- Prerequisites
- Step 1: Install WireGuard
- Step 2: Generate Key Pairs
- Step 3: Configure the Server
- Step 4: Enable IP Forwarding
- Step 5: Open the Firewall
- Step 6: Start WireGuard
- Step 7: Configure a Client
- Step 8: Add the Client to the Server
- Step 9: Connect and Test
- Connecting Mobile Devices
- Full Tunnel vs Split Tunnel
- Adding More Clients
- Troubleshooting
- Security Tips
WireGuard is a modern VPN protocol that has largely replaced OpenVPN for self-hosted setups. It's faster, simpler to configure, uses less code (which means less attack surface), and is built directly into the Linux kernel. If you've been putting off setting up a VPN because it seemed complicated — WireGuard is the reason to revisit that decision.
This guide walks through the complete setup: server installation, key generation, configuration, firewall rules, and client setup. It covers both the "route all traffic through the VPN" use case (useful when you're on public WiFi) and the "access your home network remotely" use case (useful for homelab access from anywhere).
What Is WireGuard?
A VPN (Virtual Private Network) creates an encrypted tunnel between two devices over the internet. Traffic flowing through that tunnel is encrypted and appears to come from the VPN server's IP address rather than your actual location.
WireGuard is one implementation of this concept. What makes it different from older options like OpenVPN or IPSec:
- It's built into the Linux kernel. Since kernel 5.6 (released in 2020), WireGuard is part of the kernel itself — not a third-party module you install separately. On any modern Linux distribution, it's already there.
- Minimal codebase. WireGuard is roughly 4,000 lines of code. OpenVPN is over 400,000. Less code means fewer places for bugs to hide, faster security audits, and a smaller attack surface.
- Modern cryptography. WireGuard uses ChaCha20 for encryption, Poly1305 for authentication, and Curve25519 for key exchange. These are strong, modern algorithms with no legacy cipher negotiation — you can't accidentally misconfigure it into using something weak.
- Fast. WireGuard regularly saturates gigabit links on modest hardware. OpenVPN is often CPU-bottlenecked at a fraction of that speed.
- Roaming support. If you switch from WiFi to mobile data while connected, WireGuard reconnects automatically. OpenVPN drops and needs a manual reconnect.
The tradeoff: WireGuard is intentionally minimal. It has no built-in user authentication system, no dynamic IP assignment (you manually assign IPs to each client), and no GUI. For a self-hosted homelab VPN, these limitations don't matter. For a large enterprise with hundreds of users, you'd need to build a management layer on top.
How WireGuard Works
WireGuard uses public/private key pairs — similar conceptually to SSH keys — for authentication. Every device (server and client alike) generates its own key pair. The private key never leaves the device. The public key is shared with the other side.
When a client connects to a server, WireGuard checks the client's public key against a list of allowed peers. If the key is recognized, the connection is established. If not, WireGuard silently drops the packet — the server doesn't even acknowledge the connection attempt. This means port scanning returns nothing, which is a nice security property.
WireGuard also uses a concept called AllowedIPs that serves two purposes at once: it controls which traffic is routed through the tunnel, and it acts as an access control list for which source IPs are accepted from each peer. A single setting does both jobs.
One important note on terminology: WireGuard itself calls all connected devices "peers" — there's no technical distinction between server and client. The "server" is just the peer with a fixed public IP and a listening port. The "client" is the peer that initiates the connection. This guide uses server/client for clarity.
Prerequisites
For this guide you'll need:
- A server with a public IP address — a VPS, a cloud instance, or a machine at home with port forwarding configured on your router. The server needs to be reachable from the internet on a UDP port (51820 by default).
- Linux on the server running kernel 5.6 or newer. Ubuntu 22.04 LTS, Debian 12, Fedora 40, and Arch all work perfectly. Check your kernel version with
uname -r. - Root or sudo access on the server.
- A client — another Linux machine, a Windows PC, a Mac, an Android phone, or an iPhone. WireGuard has official clients for all of these.
If you're using a homelab server rather than a VPS: you'll need to configure port forwarding on your home router to send UDP traffic on port 51820 to your server's local IP. You'll also need either a static public IP or a dynamic DNS (DDNS) service so you can reliably reach your server when away from home. Services like Cloudflare DDNS or DuckDNS are free options.
Step 1: Install WireGuard
On Ubuntu or Debian:
sudo apt update && sudo apt install wireguard -y
On Fedora:
sudo dnf install wireguard-tools -y
On CentOS/RHEL:
sudo yum install epel-release elrepo-release -y
sudo yum install kmod-wireguard wireguard-tools -y
On Arch:
sudo pacman -S wireguard-tools
Verify the installation:
wg --version
You should see something like wireguard-tools v1.0.20210914. The kernel module itself is already there on modern systems — the tools package just gives you the wg and wg-quick commands to configure it.
Step 2: Generate Key Pairs
On the server, generate the server's key pair. The umask 077 command is important — it ensures the generated key files are readable only by root.
cd /etc/wireguard
umask 077
# Generate server private key, then derive the public key from it
wg genkey | tee server_private.key | wg pubkey > server_public.key
# Verify the files exist and have correct permissions
ls -la /etc/wireguard/*.key
You should see two files, both owned by root with permissions 600 (readable only by owner). View the keys:
cat server_private.key # Keep this secret — never share it
cat server_public.key # This is safe to share with clients
The keys look like base64-encoded strings, about 44 characters long. That's normal.
Now generate a key pair for your first client. You can do this on the server for convenience, then securely transfer the private key to the client — or generate it directly on the client machine using the same commands.
# Still on the server, in /etc/wireguard
wg genkey | tee client1_private.key | wg pubkey > client1_public.key
Step 3: Configure the Server
Create the server configuration file. Replace SERVER_PRIVATE_KEY with the actual content of server_private.key:
sudo nano /etc/wireguard/wg0.conf
[Interface]
# The server's private key
PrivateKey = SERVER_PRIVATE_KEY_HERE
# The IP address of the VPN interface on the server
# /24 means the VPN subnet is 10.10.0.0/24
Address = 10.10.0.1/24
# The UDP port WireGuard listens on
ListenPort = 51820
# NAT rules: allow clients to reach the internet through the server
# Replace eth0 with your server's actual public network interface
# Check with: ip route show default
PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
A few things to note:
Address = 10.10.0.1/24 assigns the server an IP of 10.10.0.1 within the VPN subnet 10.10.0.0/24. Your clients will get IPs in the 10.10.0.2–10.10.0.254 range. You can use any private IP range here — 10.x.x.x, 172.16-31.x.x, or 192.168.x.x — as long as it doesn't conflict with your existing network.
PostUp and PostDown are shell commands that run when the WireGuard interface comes up or goes down. These iptables rules enable NAT (Network Address Translation) so clients can route internet traffic through the server. The %i is replaced by the interface name (wg0).
Important: Replace eth0 in the PostUp/PostDown lines with your server's actual public network interface name. Find it with:
ip route show default
The output will show something like default via 1.2.3.4 dev ens3 — use the interface name after dev. Common names are eth0, ens3, enp1s0, or ens160 depending on your system.
Set correct permissions on the config file:
sudo chmod 600 /etc/wireguard/wg0.conf
Step 4: Enable IP Forwarding
By default, Linux doesn't forward packets between network interfaces. To let VPN clients route their traffic through the server to the internet, you need to enable IP forwarding.
Enable it immediately and make it persistent across reboots:
# Enable now
sudo sysctl -w net.ipv4.ip_forward=1
# Make it persist across reboots
echo "net.ipv4.ip_forward=1" | sudo tee -a /etc/sysctl.d/99-wireguard.conf
sudo sysctl -p /etc/sysctl.d/99-wireguard.conf
Verify it's on:
sysctl net.ipv4.ip_forward
# Should output: net.ipv4.ip_forward = 1
Without IP forwarding enabled, clients will connect to the VPN successfully but traffic won't reach the internet. It's one of the most common gotchas in WireGuard setups.
Step 5: Open the Firewall
WireGuard uses UDP — not TCP. Make sure you're opening a UDP port, not TCP.
If you're using UFW (common on Ubuntu):
sudo ufw allow 51820/udp
sudo ufw status
If you're using firewalld (common on Fedora/RHEL):
sudo firewall-cmd --permanent --add-port=51820/udp
sudo firewall-cmd --reload
If you're using raw iptables:
sudo iptables -A INPUT -p udp --dport 51820 -j ACCEPT
If you're on a cloud provider (AWS, DigitalOcean, Hetzner, etc.), also check that your cloud security group or firewall allows inbound UDP on 51820. Cloud providers often have a firewall layer separate from the OS-level firewall.
Step 6: Start WireGuard
# Bring up the WireGuard interface
sudo wg-quick up wg0
# Enable it to start automatically at boot
sudo systemctl enable wg-quick@wg0
# Check the status
sudo wg show
The wg show command is your main diagnostic tool. It shows the server's public key, the listening port, and all connected peers. Right now you should see the interface up with no peers yet — we'll add those next.
If you need to restart WireGuard after making config changes:
sudo wg-quick down wg0 && sudo wg-quick up wg0
Step 7: Configure a Client
Now set up the client. This example covers a Linux client — skip to the Mobile section if you're setting up a phone.
Install WireGuard on the client using the same install commands from Step 1.
Create the client config file. You'll need two pieces of information from the server:
- The server's public key (
cat /etc/wireguard/server_public.keyon the server) - The server's public IP address
And from the key generation step:
- The client's private key (
client1_private.key)
On the client machine, create /etc/wireguard/wg0.conf:
[Interface]
# This client's private key
PrivateKey = CLIENT1_PRIVATE_KEY_HERE
# This client's IP address in the VPN subnet
Address = 10.10.0.2/32
# DNS server to use when VPN is active
# 1.1.1.1 is Cloudflare's public DNS — or use your own Pi-hole IP
DNS = 1.1.1.1
[Peer]
# The server's public key
PublicKey = SERVER_PUBLIC_KEY_HERE
# The server's public IP and WireGuard port
Endpoint = YOUR.SERVER.IP:51820
# Route ALL traffic through the VPN (full tunnel)
# To only access the VPN subnet, use 10.10.0.0/24 instead
AllowedIPs = 0.0.0.0/0
# Send a keepalive packet every 25 seconds
# Required when the client is behind NAT (which is almost always)
PersistentKeepalive = 25
Set permissions:
sudo chmod 600 /etc/wireguard/wg0.conf
Address = 10.10.0.2/32 — the /32 means this specific IP only (not a range). Clients should use /32, not /24.
AllowedIPs = 0.0.0.0/0 means route all traffic through the tunnel. This is a full tunnel — your entire internet connection goes through the VPN server. If you only want to access resources on the VPN network (not route all internet traffic), use the VPN subnet instead: 10.10.0.0/24.
PersistentKeepalive = 25 sends a small packet every 25 seconds to keep the connection alive through NAT. Almost every home network and mobile connection uses NAT, so include this unless you know you don't need it.
Step 8: Add the Client to the Server
Back on the server, add the client as a peer. Edit /etc/wireguard/wg0.conf and add a [Peer] block:
[Peer]
# A comment to identify this client
# Client: laptop
PublicKey = CLIENT1_PUBLIC_KEY_HERE
AllowedIPs = 10.10.0.2/32
The AllowedIPs on the server side means: accept traffic that appears to come from 10.10.0.2, and only from this peer. Each client should have its own unique IP here.
After editing the server config, you have two options to apply the change:
Option A — Restart WireGuard (brief interruption):
sudo wg-quick down wg0 && sudo wg-quick up wg0
Option B — Add the peer without any downtime:
sudo wg set wg0 peer CLIENT1_PUBLIC_KEY allowed-ips 10.10.0.2/32
Option B adds the peer live. To make it persistent, also save the running config back to the file:
sudo wg-quick save wg0
Step 9: Connect and Test
On the client:
# Connect
sudo wg-quick up wg0
# Check the connection
sudo wg show
# Test connectivity to the server's VPN IP
ping 10.10.0.1
# If using full tunnel, verify your traffic is going through the VPN
# This should show the server's public IP, not your actual IP
curl ifconfig.me
On the server, wg show should now show the client as a peer with a recent "latest handshake" timestamp and data transferred. If the handshake happened, the tunnel is working.
To disconnect:
sudo wg-quick down wg0
To connect automatically at boot:
sudo systemctl enable wg-quick@wg0
Connecting Mobile Devices
WireGuard has official apps for Android and iOS, both available from their respective app stores for free.
The easiest way to get a mobile device connected is via QR code. First, create a config file for the phone (same format as the Linux client config), then generate a QR code from it.
Install qrencode on the server:
# Ubuntu/Debian
sudo apt install qrencode -y
# Fedora
sudo dnf install qrencode -y
Generate and display the QR code in your terminal:
qrencode -t ansiutf8 < /etc/wireguard/phone.conf
Open the WireGuard app on your phone, tap the + button, select Scan from QR Code, and point the camera at your terminal. The entire config imports in one scan.
Remember to add the phone's public key (found in the app after importing) to the server's wg0.conf as a new [Peer] block with its own unique IP address (e.g., 10.10.0.3/32).
Full Tunnel vs Split Tunnel
This is an important decision that affects how the VPN works day-to-day.
Full tunnel (AllowedIPs = 0.0.0.0/0 on the client): All internet traffic goes through the VPN server. Your internet activity appears to come from the server's IP. Useful for privacy on public WiFi or if you want to appear to be in a different location. Downside: your internet speed is capped by the VPN server's bandwidth.
Split tunnel (AllowedIPs = 10.10.0.0/24 on the client): Only traffic destined for the VPN subnet goes through the tunnel. Regular internet traffic goes directly from your device. Useful if you just want to access your homelab remotely without routing everything through the VPN. Downside: doesn't provide privacy on public networks.
For homelab access (the most common use case for self-hosters), split tunnel is usually the right choice. You can SSH into your servers, access Proxmox, reach internal services — all without your normal browsing going through the tunnel.
To use split tunnel, change the client's AllowedIPs to include only the subnets you want to reach through the VPN:
# Only route VPN subnet traffic through the tunnel
AllowedIPs = 10.10.0.0/24
# Or if you also want to access your home LAN (192.168.1.0/24):
AllowedIPs = 10.10.0.0/24, 192.168.1.0/24
Adding More Clients
Every new device needs:
- Its own key pair (generate with
wg genkey | tee clientN_private.key | wg pubkey > clientN_public.key) - Its own unique IP address in the VPN subnet (10.10.0.3, 10.10.0.4, etc.)
- A config file using the server's public key and endpoint
- A
[Peer]block added to the server'swg0.conf
There's no limit to the number of peers. A [Peer] block on the server config looks the same for every client — just a public key and an AllowedIPs entry.
Example server config with three clients:
[Interface]
PrivateKey = SERVER_PRIVATE_KEY
Address = 10.10.0.1/24
ListenPort = 51820
PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
# Laptop
[Peer]
PublicKey = LAPTOP_PUBLIC_KEY
AllowedIPs = 10.10.0.2/32
# Phone
[Peer]
PublicKey = PHONE_PUBLIC_KEY
AllowedIPs = 10.10.0.3/32
# Work desktop
[Peer]
PublicKey = DESKTOP_PUBLIC_KEY
AllowedIPs = 10.10.0.4/32
Troubleshooting
WireGuard is silent by design — it doesn't log much, and it drops invalid packets without responding. This makes debugging quieter than with other VPN software. Here's the diagnostic sequence:
Check WireGuard is running and peers are visible:
sudo wg show
Look for latest handshake under each peer. If a handshake has happened, the tunnel is working at the cryptographic layer. If there's no handshake, the connection isn't getting through.
Check the port is listening:
ss -ulnp | grep 51820
Should show WireGuard listening on UDP 51820. If nothing shows, the interface isn't up.
Check IP forwarding is on:
sysctl net.ipv4.ip_forward
# Should be: net.ipv4.ip_forward = 1
This is the most commonly missed step. Without it, clients connect but internet traffic doesn't work.
Check the firewall allows UDP 51820:
# UFW
ufw status | grep 51820
# firewalld
firewall-cmd --list-ports | grep 51820
# iptables
iptables -L -n | grep 51820
Check the network interface name in PostUp/PostDown:
ip route show default
The interface name after dev must match what's in your PostUp/PostDown iptables rules. If you wrote eth0 but your server uses ens3, NAT won't work.
Check NAT rules are applied:
sudo iptables -t nat -L -n
You should see a MASQUERADE rule in the POSTROUTING chain after the WireGuard interface comes up.
MTU issues — traffic drops on large packets:
If the tunnel works but large transfers fail or web pages load partially, the MTU (Maximum Transmission Unit) may need adjusting. WireGuard's default MTU is 1420 bytes. Some networks require lower. Add to the client's [Interface] section:
MTU = 1280
DNS isn't working through the tunnel:
Make sure the DNS = line is in the client's [Interface] section. On Linux clients using systemd-resolved, verify it's applied:
resolvectl status
Security Tips
Never share private keys. Each device generates its own key pair. If a device is compromised, revoke it by removing its [Peer] block from the server config and reloading. The other devices are unaffected.
Use umask 077 when generating keys. This prevents other users on the system from reading the key files.
Set 600 permissions on config files. WireGuard config files contain private keys. Run chmod 600 /etc/wireguard/*.conf and chmod 600 /etc/wireguard/*.key to make them readable only by root.
Restrict AllowedIPs on the server. Each client's [Peer] block on the server should use /32 (a single IP), not a broader range. This prevents one client from spoofing another's IP.
Consider changing the default port. Port 51820 is the well-known WireGuard default and gets scanned. Changing it to a random high port (e.g., 47382) reduces noise without meaningfully reducing security. Update both the server's ListenPort and the client's Endpoint.
Rotate keys periodically. For long-lived servers, it's good practice to regenerate key pairs occasionally. The process is: generate new keys, update the server with the new public key, update the client with the new private key, reload both sides.
WireGuard is one of those rare tools that manages to be both simpler and better than what it replaces. Once you've gone through this setup once, adding new devices takes about two minutes. If you're running a homelab, being able to access it securely from anywhere — coffee shop, hotel, wherever — is worth the one-time setup effort.