How to Self-Host a WireGuard VPN Server: Complete Guide (No Port Forwarding Needed)
WireGuard is the fastest, simplest, and most modern VPN protocol available. Built into the Linux kernel since version 5.6, it uses state-of-the-art cryptography, a codebase of under 4,000 lines, and consistently outperforms OpenVPN in both throughput and latency. This guide covers every aspect of self-hosting a WireGuard server: how WireGuard works, installing and configuring the server on Ubuntu and Raspberry Pi, generating keys, writing correct wg0.conf files with PostUp and PostDown iptables rules, configuring the network interface and IP forwarding, adding multiple peers with QR codes, understanding the difference between full-tunnel and split-tunnel setups, using PresharedKey for post-quantum resistance, and solving the biggest home-server obstacle: CGNAT. If your ISP uses CGNAT and port forwarding does not work, the guide explains exactly why, and how Localtonet's native UDP tunnel solves it.
What Is WireGuard?
WireGuard is a modern VPN protocol and its reference implementation in the Linux kernel was created by Jason A. Donenfeld and first published in 2016. Unlike OpenVPN (around 70,000 lines of code) and IPsec (even larger and more complex), WireGuard's kernel implementation is under 4,000 lines of code. This small footprint makes it far easier to audit, maintain, and verify for security vulnerabilities. In March 2020, WireGuard was merged into the Linux 5.6 kernel as a first-class citizen. On modern Linux, WireGuard requires no external daemon; the kernel module handles all cryptographic operations directly.
WireGuard uses a fixed, modern cryptographic suite with no algorithm negotiation:
| Function | Algorithm | Purpose |
|---|---|---|
| Key exchange | Curve25519 (ECDH) | Establishes shared secrets between peers |
| Symmetric encryption | ChaCha20 | Encrypts the tunnel payload |
| Authentication | Poly1305 | Authenticates and integrity-checks every packet |
| Hashing | BLAKE2s | Used in key derivation and handshake |
| Key derivation | HKDF | Derives session keys from the handshake |
WireGuard exclusively uses UDP for all traffic. The default port is UDP 51820. WireGuard does not support TCP by design: tunneling TCP inside TCP (TCP-over-TCP) causes severe performance degradation due to the TCP meltdown problem, where duplicate retransmission logic at both layers cascades and collapses throughput. The WireGuard project explicitly documents this as a deliberate design decision. If you need WireGuard over a network that blocks UDP, external tools like udp2raw can wrap WireGuard's UDP packets in TCP, but this is separate from WireGuard itself.
WireGuard vs OpenVPN: Key Differences
| Feature | WireGuard | OpenVPN |
|---|---|---|
| Codebase size | ~4,000 lines (Linux kernel module) | ~70,000 lines |
| Transport protocol | UDP only | UDP or TCP |
| Default port | UDP 51820 | UDP/TCP 1194 |
| Encryption | Fixed: ChaCha20-Poly1305 | Configurable: AES-256, ChaCha20, and many others via OpenSSL |
| Key exchange | Curve25519 only | Configurable: RSA, ECDH, and others |
| Handshake time | ~0.5 to 1 second | 3 to 8 seconds |
| Speed (throughput) | Consistently achieves 95%+ of bare-metal network speed | Typically maxes at ~75% due to OpenSSL overhead and user-space processing |
| Latency | Low (kernel-level, no user-space hop) | Higher (user-space daemon adds latency) |
| Roaming (IP change) | Handles network changes silently; tunnel survives Wi-Fi to mobile data switch | Reconnection required on IP change |
| Configuration complexity | Single config file, minimal options | Many options, certificate management, CA required |
| Kernel integration | Native Linux kernel module since 5.6 (March 2020) | User-space daemon (tun/tap interface) |
What You Can Do with a Self-Hosted WireGuard VPN
The CGNAT Problem: Why Port Forwarding May Not Work for You
Traditional WireGuard server setup requires opening UDP port 51820 on your router (port forwarding) so that clients can connect from outside your home network. This works if your ISP gives you a real public IPv4 address. Many ISPs in 2026 do not.
CGNAT (Carrier Grade NAT) means your ISP places many customers behind a shared public IP address on their own NAT layer, before your home router. From the internet's perspective, your connection is invisible. You cannot port forward through the ISP's NAT layer because you do not control it. Even if you open every port on your home router, incoming connections are blocked by the ISP's NAT before they reach you.
How to check if you are behind CGNAT
Compare your router's WAN IP address (visible in your router's admin panel) with your actual public IP (check whatismyip.com). If they are different, you are behind CGNAT. Also check: if your router's WAN IP starts with 100.64.x.x through 100.127.x.x, that is the RFC 6598 CGNAT address range, and port forwarding will definitely not work.
There are three practical solutions for WireGuard behind CGNAT:
- Pay your ISP for a static public IP: Works, but costs money and is not available from all ISPs.
- Use a VPS as a relay: Configure a cheap cloud server as a WireGuard relay. Your home machine connects out to the VPS; clients connect to the VPS. Full control but requires server management.
- Localtonet UDP tunnel (simplest): Localtonet creates an outbound UDP tunnel that exposes your WireGuard port through a public endpoint without any inbound port opening required. This is the method covered in this guide.
Requirements
| Component | Minimum | Notes |
|---|---|---|
| OS (server) | Linux with kernel 5.6+ (WireGuard built-in) | Ubuntu 22.04, 24.04, Debian 12, Raspberry Pi OS Bookworm all include WireGuard in the kernel. Older kernels need the wireguard-dkms package. |
| CPU | Any 64-bit | WireGuard is kernel-level; CPU usage is minimal even under heavy load. |
| RAM | 128 MB | WireGuard uses negligible RAM. The kernel module stays loaded but idle memory use is near zero. |
| Network | Any internet connection | Works with dynamic IP (use DDNS). Works behind CGNAT with Localtonet UDP tunnel. |
| Clients | WireGuard app on any OS | Official apps for Android, iOS, Windows, macOS, Linux. All free and open source. |
Step 1: Install WireGuard on the Server
WireGuard is included in the standard Linux kernel from version 5.6 onward. On Ubuntu 22.04 and 24.04, the wireguard package installs the userspace tools (wg and wg-quick) that manage the kernel module:
sudo apt update
sudo apt install -y wireguard wireguard-tools
Verify the kernel module and tools are available:
sudo modprobe wireguard
lsmod | grep wireguard
Expected output:
wireguard 118784 0
curve25519_x86_64 36864 1 wireguard
libchacha20poly1305 16384 1 wireguard
ip6_udp_tunnel 16384 1 wireguard
udp_tunnel 24576 1 wireguard
# Check wg-quick version:
wg --version
Expected output:
wireguard-tools v1.0.20210914 - https://git.zx2c4.com/wireguard-tools/
Step 2: Generate Server Keys
WireGuard uses public-key cryptography. Each peer (server and every client) has its own private key and a corresponding public key. The private key is never shared. The public key is what you add to the other side's configuration.
# Work inside the wireguard config directory:
cd /etc/wireguard
# Generate the server private key:
wg genkey | sudo tee server_private.key | wg pubkey | sudo tee server_public.key
# Set strict permissions on the private key:
sudo chmod 600 /etc/wireguard/server_private.key
# View the keys:
sudo cat /etc/wireguard/server_private.key
sudo cat /etc/wireguard/server_public.key
Expected output (your values will be different):
# server_private.key (example, never share this):
aBcDeFgHiJkLmNoPqRsTuVwXyZ0123456789ABCDEF=
# server_public.key (this is what clients need):
ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdef=
Keep server_private.key on the server only. Never paste it into a client config, share it by email, or commit it to a repository. If it is compromised, regenerate both keys and update all client configs immediately. The public key is safe to share; it is designed to be distributed to peers.
Step 3: Find Your Server's Network Interface Name
The WireGuard server's PostUp and PostDown rules must reference the actual name of your internet-facing network interface. This is typically eth0 on older Ubuntu systems but ens3, ens18, enp2s0, or similar on newer ones. Never assume eth0.
# Find the interface connected to the internet:
ip -o -4 route show to default | awk '{print $5}'
Expected output (example, yours may differ):
ens3
Note this name. You will use it in the PostUp and PostDown lines of the server config.
Step 4: Enable IP Forwarding
For WireGuard to route client traffic through the server (full-tunnel mode), the Linux kernel must be configured to forward packets between network interfaces. Without this, packets arriving on the wg0 interface cannot be sent out on ens3 toward the internet.
# Enable IPv4 forwarding immediately (does not persist across reboots):
sudo sysctl -w net.ipv4.ip_forward=1
# Make it permanent (persists across reboots):
echo "net.ipv4.ip_forward = 1" | sudo tee /etc/sysctl.d/99-wireguard.conf
sudo sysctl -p /etc/sysctl.d/99-wireguard.conf
Verify it is active:
cat /proc/sys/net/ipv4/ip_forward
Expected output:
1
Step 5: Create the Server Configuration File
The WireGuard server configuration file lives at /etc/wireguard/wg0.conf. The interface name wg0 comes from the filename: wg-quick creates a network interface named after the config file. This file has an [Interface] section (the server itself) and one [Peer] section for each connected client.
sudo nano /etc/wireguard/wg0.conf
Paste the following, replacing values with your own:
[Interface]
# The private key of this server (from server_private.key):
PrivateKey = PASTE_SERVER_PRIVATE_KEY_HERE
# The VPN subnet IP address assigned to this server:
Address = 10.0.0.1/24
# WireGuard listens on this UDP port:
ListenPort = 51820
# Recommended MTU for WireGuard tunnels:
MTU = 1420
# Run when the interface comes up:
# Replace ens3 with your actual internet-facing interface name from Step 3
PostUp = iptables -A FORWARD -i %i -j ACCEPT
PostUp = iptables -A FORWARD -o %i -j ACCEPT
PostUp = iptables -t nat -A POSTROUTING -o ens3 -j MASQUERADE
# Run when the interface goes down (reverses the above rules):
PostDown = iptables -D FORWARD -i %i -j ACCEPT
PostDown = iptables -D FORWARD -o %i -j ACCEPT
PostDown = iptables -t nat -D POSTROUTING -o ens3 -j MASQUERADE
# Peer sections are added here as you add clients.
# Each client gets its own [Peer] block (see Step 6).
# Secure the config file:
sudo chmod 600 /etc/wireguard/wg0.conf
What PostUp and PostDown do
These lines run shell commands when the WireGuard interface is brought up (PostUp) or torn down (PostDown). The three iptables rules accomplish:
FORWARD -i %i -j ACCEPT: Allow packets arriving on the wg0 interface to be forwarded. The%iis a wg-quick macro that expands to the interface name (wg0).FORWARD -o %i -j ACCEPT: Allow packets going out on wg0 (return traffic to VPN clients) to be forwarded.nat POSTROUTING -o ens3 -j MASQUERADE: Apply NAT masquerading to all traffic leaving throughens3. This makes VPN client traffic appear to come from the server's own IP address when it reaches the internet, just like how your home router NATs your devices.
The PostDown rules are identical but use -D (delete) instead of -A (append) to cleanly remove the rules when WireGuard stops.
Step 6: Generate Client Keys and Add Peers
Each client (peer) needs its own key pair. You can generate keys on the server and transfer the config to the client, or generate them on the client device and only give the server the public key. The most common approach is generating everything on the server and then transferring the complete client config as a QR code or file.
Generate client keys
cd /etc/wireguard
# Generate keys for the first client (e.g., a phone):
wg genkey | sudo tee client1_private.key | wg pubkey | sudo tee client1_public.key
sudo chmod 600 /etc/wireguard/client1_private.key
# Optional but recommended: generate a preshared key for this peer pair:
wg genpsk | sudo tee client1_preshared.key
sudo chmod 600 /etc/wireguard/client1_preshared.key
Add the peer to the server config
sudo nano /etc/wireguard/wg0.conf
Add this block at the end of the file:
[Peer]
# Friendly comment to identify this peer:
# Phone - Alice
# The client's public key:
PublicKey = PASTE_CLIENT1_PUBLIC_KEY_HERE
# The preshared key (optional, provides post-quantum resistance):
PresharedKey = PASTE_CLIENT1_PRESHARED_KEY_HERE
# The VPN IP address(es) this client is allowed to use:
AllowedIPs = 10.0.0.2/32
What PresharedKey does and why to use it
PresharedKey adds a symmetric layer of encryption on top of WireGuard's Curve25519 key exchange. This provides post-quantum resistance: even if a future quantum computer can break elliptic curve cryptography, the attacker would still need to crack the pre-shared symmetric key to decrypt the traffic. The preshared key is unique per peer pair, generated with wg genpsk, and must be identical in both the server's [Peer] block and the client's [Peer] block. It is optional but takes one command to add and meaningfully strengthens the security posture.
Step 7: Create the Client Configuration File
Read each key value to create the client config:
# Read the values you need:
sudo cat /etc/wireguard/client1_private.key
sudo cat /etc/wireguard/server_public.key
sudo cat /etc/wireguard/client1_preshared.key
Create the client config file:
sudo nano /etc/wireguard/client1.conf
[Interface]
# The client's own private key:
PrivateKey = PASTE_CLIENT1_PRIVATE_KEY_HERE
# The VPN IP address assigned to this client:
Address = 10.0.0.2/32
# DNS server to use while VPN is active:
# Use the server's VPN IP (10.0.0.1) if Pi-hole or AdGuard runs on the server,
# or a public DNS like 1.1.1.1:
DNS = 1.1.1.1
# Recommended MTU for WireGuard tunnels:
MTU = 1420
[Peer]
# The server's public key:
PublicKey = PASTE_SERVER_PUBLIC_KEY_HERE
# The preshared key (must match the server's config for this client):
PresharedKey = PASTE_CLIENT1_PRESHARED_KEY_HERE
# Full-tunnel: route ALL traffic through VPN (0.0.0.0/0 = all IPv4, ::/0 = all IPv6):
AllowedIPs = 0.0.0.0/0, ::/0
# The server's public address and port.
# Replace with your actual public IP or domain / Localtonet address:
Endpoint = YOUR_SERVER_PUBLIC_IP:51820
# Send a keepalive packet every 25 seconds.
# Required when the client is behind NAT to keep the tunnel alive:
PersistentKeepalive = 25
Full Tunnel vs Split Tunnel: AllowedIPs Explained
The AllowedIPs field in a client's [Peer] block is the most important and most misunderstood setting in WireGuard. It serves two purposes simultaneously: it defines which destination IP ranges the client routes through the tunnel, AND it acts as an access control list determining which source IPs the server will accept packets from for this peer.
Full Tunnel: AllowedIPs = 0.0.0.0/0, ::/0
- ALL traffic (internet + home network) goes through the VPN
- Your public IP becomes your server's IP
- DNS goes through the VPN (prevents DNS leaks)
- Best for: public Wi-Fi protection, bypassing geo-restrictions
- Downside: all your internet traffic routes through your home connection speed
Split Tunnel: AllowedIPs = 10.0.0.0/24
- Only traffic destined for the VPN subnet (10.0.0.0/24) goes through the tunnel
- Regular internet traffic goes directly (not through VPN)
- Best for: accessing home network resources without slowing internet traffic
- Your public IP for regular browsing remains your local ISP's IP
- Faster internet for non-home-network traffic
| Use Case | AllowedIPs Value | Effect |
|---|---|---|
| Full tunnel (all traffic) | 0.0.0.0/0, ::/0 | All IPv4 and IPv6 traffic through VPN |
| Split tunnel (VPN subnet only) | 10.0.0.0/24 | Only VPN-internal traffic through tunnel |
| Split tunnel + home LAN | 10.0.0.0/24, 192.168.1.0/24 | VPN subnet + home LAN, internet direct |
| Access specific home server only | 10.0.0.0/24, 192.168.1.50/32 | VPN subnet + one specific home machine |
Step 8: Start WireGuard and Enable Autostart
# Bring the interface up:
sudo wg-quick up wg0
# Enable autostart on boot:
sudo systemctl enable wg-quick@wg0
Verify the interface is running:
sudo wg show
Expected output (before any clients connect):
interface: wg0
public key: ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdef=
private key: (hidden)
listening port: 51820
peer: PASTE_CLIENT1_PUBLIC_KEY_HERE
preshared key: (hidden)
allowed ips: 10.0.0.2/32
# Check the interface is up in ip:
ip address show wg0
Expected output:
4: wg0: mtu 1420 qdisc noqueue state UNKNOWN
link/none
inet 10.0.0.1/24 scope global wg0
valid_lft forever preferred_lft forever
Step 9: Generate a QR Code for Mobile Clients
The WireGuard mobile apps for Android and iOS can import a client config by scanning a QR code. This is the easiest way to add clients without copying config files manually.
sudo apt install -y qrencode
# Generate the QR code for client1 (displays in terminal):
sudo qrencode -t ansiutf8 < /etc/wireguard/client1.conf
A QR code appears in the terminal. Open the WireGuard app on your phone, tap the + button, select Scan from QR Code, and scan the code. The full client configuration is embedded in the QR code.
Do not scan QR codes in public or on video calls. The QR code encodes the entire client config, including the private key. Anyone who scans it or captures a photo of the terminal gains full access to that VPN peer.
Step 10: Adding More Clients
Each additional client needs its own key pair, a unique VPN IP address, and its own [Peer] block in the server config. Use sequential IPs: 10.0.0.2/32 for client 1, 10.0.0.3/32 for client 2, and so on.
cd /etc/wireguard
# Generate keys for client 2 (e.g., a laptop):
wg genkey | sudo tee client2_private.key | wg pubkey | sudo tee client2_public.key
sudo chmod 600 /etc/wireguard/client2_private.key
wg genpsk | sudo tee client2_preshared.key
sudo chmod 600 /etc/wireguard/client2_preshared.key
Add to /etc/wireguard/wg0.conf:
[Peer]
# Laptop - Bob
PublicKey = PASTE_CLIENT2_PUBLIC_KEY_HERE
PresharedKey = PASTE_CLIENT2_PRESHARED_KEY_HERE
AllowedIPs = 10.0.0.3/32
Apply the new peer without restarting WireGuard (no downtime for existing clients):
sudo wg addconf wg0 <(sudo wg-quick strip wg0)
Or simply reload the interface:
sudo wg-quick down wg0 && sudo wg-quick up wg0
Installation on Raspberry Pi
The Raspberry Pi is an excellent low-power WireGuard server. WireGuard is built into the Raspberry Pi OS kernel (Bookworm and later). The Raspberry Pi 4 and Pi 5 easily handle dozens of WireGuard peers. Even the Pi Zero 2W can run a personal WireGuard instance with minimal load.
# Install on Raspberry Pi OS (Bookworm):
sudo apt update
sudo apt install -y wireguard wireguard-tools
# Verify the module:
lsmod | grep wireguard
All the configuration steps above (key generation, wg0.conf, PostUp/PostDown, IP forwarding) are identical on Raspberry Pi OS. The only common difference is the network interface name: Raspberry Pi connected via Ethernet typically uses eth0; via Wi-Fi it uses wlan0. Confirm with:
ip -o -4 route show to default | awk '{print $5}'
A common Raspberry Pi WireGuard setup: the Pi runs on your home LAN, WireGuard clients use split-tunnel (AllowedIPs = 10.0.0.0/24, 192.168.1.0/24), and when you connect from outside, you can access any device on your home LAN (NAS, Plex, printer, etc.) as if you were there. Regular internet traffic goes direct. This setup uses almost no bandwidth on your home connection for normal browsing.
Connecting Clients: All Platforms
Android and iOS
Install the official WireGuard app from the Play Store or App Store. Tap the + button and select Scan from QR Code. Scan the QR code generated in Step 9. The tunnel appears in the app. Toggle it on to connect.
Windows
Download the official WireGuard installer from wireguard.com/install. Open WireGuard, click Add Tunnel, select Import tunnel(s) from file, and select the client1.conf file. Click Activate to connect.
macOS
Install WireGuard from the Mac App Store. Open it, click the + button, choose Add Empty Tunnel or Import tunnel(s) from file, and paste or import the client1.conf content. Activate the tunnel from the menu bar icon.
Linux Client
sudo apt install wireguard
sudo cp client1.conf /etc/wireguard/wg0.conf
sudo wg-quick up wg0
sudo systemctl enable wg-quick@wg0
Exposing WireGuard Behind CGNAT with Localtonet UDP Tunnel
If you are behind CGNAT or cannot open UDP port 51820 on your router, Localtonet's native UDP tunnel gives your WireGuard server a public UDP endpoint that clients can connect to from anywhere. WireGuard clients point their Endpoint to the Localtonet address instead of your home IP.
Unlike Cloudflare Tunnel (which only supports TCP/HTTP) and many other tunnel services, Localtonet supports UDP tunnels natively. This is essential for WireGuard, which exclusively uses UDP. The tunnel carries WireGuard's encrypted UDP packets transparently.
Install Localtonet
# Linux / Raspberry Pi OS:
curl -fsSL https://localtonet.com/install.sh | sh
# Verify:
localtonet --version
Authenticate with your AuthToken
Register at localtonet.com, go to Dashboard → My Tokens, copy your AuthToken:
localtonet --authtoken YOUR_TOKEN_HERE
Create a UDP tunnel for port 51820
Go to localtonet.com/tunnel/udptcp. Fill in:
- Protocol: UDP
- IP:
127.0.0.1 - Port:
51820 - AuthToken: Select your token
Click Create, then Start. The dashboard shows a public endpoint address and port, for example: tunnel.localto.net:XXXXX
Update the Endpoint in all client configs
In each client's [Peer] block, change the Endpoint line from your home IP to the Localtonet address and port shown in the dashboard:
# Change from:
Endpoint = YOUR_HOME_IP:51820
# To (use the address and port from the Localtonet dashboard):
Endpoint = tunnel.localto.net:XXXXX
Regenerate QR codes for mobile clients and re-import into the WireGuard app after this change.
Run Localtonet as a service (always-on)
sudo localtonet --install-service --authtoken YOUR_TOKEN_HERE
sudo localtonet --start-service --authtoken YOUR_TOKEN_HERE
The tunnel starts automatically on every reboot alongside WireGuard.
Monitoring and Diagnostics: wg show
The wg show command is the primary diagnostic tool for WireGuard. It shows the state of all interfaces and peers, including the most recent handshake time, transferred bytes, and the endpoint address the server last saw each client connect from.
sudo wg show
Expected output after a client connects:
interface: wg0
public key: ABCDEFGHIJKLMNOPQRSTUVWXYZ=
private key: (hidden)
listening port: 51820
peer: CLIENT1_PUBLIC_KEY=
preshared key: (hidden)
endpoint: 203.0.113.45:54321
allowed ips: 10.0.0.2/32
latest handshake: 42 seconds ago
transfer: 1.24 MiB received, 8.75 MiB sent
Key fields to check when troubleshooting:
| Field | What It Means | Problem Indicator |
|---|---|---|
| latest handshake | How long ago the last cryptographic handshake occurred between server and this peer | If missing or older than 3 minutes for an "active" connection, the tunnel is broken |
| transfer | Bytes received and sent for this peer | If both are 0 after connecting, no traffic is flowing through the tunnel |
| endpoint | The client's actual public IP and port as seen by the server | If missing, the client has never successfully connected |
| allowed ips | The IP addresses this peer is permitted to use | Mismatch with client config causes all packets to be dropped |
Troubleshooting
| Problem | Cause | Fix |
|---|---|---|
| Client shows "Active" but no internet access (full tunnel) | IP forwarding is off, or PostUp iptables rules are wrong (wrong interface name) | Check cat /proc/sys/net/ipv4/ip_forward (must be 1). Check that the interface name in PostUp matches your actual internet interface: run ip -o -4 route show to default | awk '{print $5}' and compare. |
wg show shows no latest handshake for a peer |
Client cannot reach the server's UDP port. Firewall blocking, port not forwarded, or wrong Endpoint in client config. | Confirm UDP 51820 is open on the server firewall: sudo ss -ulnp | grep 51820. If behind CGNAT, use Localtonet UDP tunnel. Check the Endpoint value in the client config is correct. |
| Handshake succeeds but no traffic flows | AllowedIPs mismatch. Client's AllowedIPs does not include the destination being reached, or server's AllowedIPs for this peer does not match the client's IP. | Check AllowedIPs in both server and client configs. For full tunnel: client needs 0.0.0.0/0, ::/0. Server needs the exact client VPN IP: 10.0.0.2/32. |
| DNS resolves slowly or not at all through VPN | DNS server in client config is not reachable through the tunnel, or is not included in AllowedIPs | If using split tunnel, ensure the DNS server IP is in AllowedIPs. For full tunnel, use 1.1.1.1 or 8.8.8.8 in the client's DNS setting. |
| Connection drops when switching from Wi-Fi to mobile data (Android/iOS) | PersistentKeepalive not set in client config | Add PersistentKeepalive = 25 to the [Peer] section of the client config. This sends a keepalive UDP packet every 25 seconds, maintaining NAT mappings and preventing the tunnel from appearing idle. |
| "RTNETLINK answers: Operation not supported" when running wg-quick up | WireGuard kernel module not loaded | Run sudo modprobe wireguard. If that fails, the kernel does not include WireGuard: install wireguard-dkms or upgrade to a kernel that includes it. |
| Large files transfer slowly through the tunnel | MTU mismatch causing packet fragmentation | Set MTU = 1420 in both server and client [Interface] sections. This is the correct value for WireGuard over standard IPv4. If still slow, try MTU = 1380. |
| Port 51820 shows no connection despite server running | UFW or iptables blocking the UDP port | If using UFW: sudo ufw allow 51820/udp. Then sudo ufw reload. Verify with sudo ss -ulnp | grep 51820 and sudo ufw status. |
Frequently Asked Questions
Why does WireGuard only use UDP and not TCP?
WireGuard's designers made an explicit decision to use UDP only because tunneling TCP inside TCP (TCP-over-TCP) causes severe performance degradation known as the TCP meltdown problem. When the inner TCP stream experiences packet loss, it triggers retransmissions. The outer TCP layer also retransmits the same data. These two retransmission mechanisms interfere destructively, causing throughput to collapse to near zero in congested conditions. WireGuard's official documentation notes this limitation explicitly and suggests that TCP tunneling, when needed for network compatibility reasons, should be handled by a separate layer above WireGuard, using tools like udp2raw.
What is the difference between AllowedIPs on the server side vs the client side?
AllowedIPs serves different purposes depending on which side it appears on. On the server in a [Peer] block, AllowedIPs means: "packets from this peer are only accepted if their source IP is within these ranges." It is an inbound access control list. If a client sends a packet from an IP not listed in their AllowedIPs, the server drops it silently. On the client in a [Peer] block, AllowedIPs means: "route outbound traffic to these destinations through the tunnel." It is a routing rule. Setting 0.0.0.0/0 routes all traffic through the VPN. Setting 10.0.0.0/24 only routes VPN-subnet traffic through the tunnel, leaving regular internet traffic direct.
Can I run WireGuard without a static IP address?
Yes, with a dynamic DNS (DDNS) service. Set up a DDNS hostname (e.g., myhome.ddns.net) that automatically updates when your home IP changes, and use that hostname as the Endpoint in client configs instead of a bare IP address. WireGuard resolves the hostname when the tunnel starts. If your IP changes while the tunnel is active, WireGuard does not automatically re-resolve it; clients need to reconnect. For CGNAT (no real public IP at all), DDNS does not help. In that case, use the Localtonet UDP tunnel approach, which provides a stable public endpoint regardless of your home IP or CGNAT status.
How many clients can a Raspberry Pi 4 handle?
WireGuard is kernel-level and extremely efficient on ARM processors. A Raspberry Pi 4 can comfortably handle 10 to 20 simultaneous active VPN peers with typical home internet traffic patterns. CPU usage stays under 5% for normal browsing and streaming workloads. The actual bottleneck is almost always your home internet uplink bandwidth, not the Pi's processing power. For personal and family use, the Pi 4 with 2 or 4 GB RAM is more than sufficient. The Pi 5 handles even higher loads if needed.
What does PersistentKeepalive do and when is it needed?
PersistentKeepalive = 25 instructs WireGuard to send an empty authenticated UDP packet to the peer every 25 seconds, even when no data is being transmitted. Without this, NAT devices on the client's network close the UDP mapping after a period of inactivity (typically 30 to 120 seconds depending on the router). When the NAT mapping closes, the server can no longer reach the client. The next time the client sends data, the handshake must restart. PersistentKeepalive prevents this by keeping the NAT mapping alive with regular small packets. It is only needed on the client side (the side behind NAT). The server, which is publicly reachable, does not need it. A value of 25 seconds is the commonly recommended interval.
Is WireGuard safe to use as a production VPN?
Yes. WireGuard was formally verified using the CryptoVerif cryptographic protocol verifier in 2019, confirming that its handshake protocol achieves the intended security properties. Both OpenVPN and WireGuard are considered secure with no known vulnerabilities in their cryptographic implementations as of 2026. WireGuard's fixed cryptographic suite (no negotiation) is actually an advantage: there is no downgrade attack surface. The tradeoff is that if a future vulnerability were found in ChaCha20 or Curve25519, WireGuard would require a protocol update (not just a configuration change), whereas OpenVPN's algorithm agility allows switching ciphers via config. In practice, both ChaCha20 and Curve25519 have excellent security records and are widely trusted.
Run Your WireGuard Server Behind CGNAT
Localtonet's native UDP tunnel gives your WireGuard server a public endpoint without port forwarding, a static IP, or a VPS. Works from any home or office internet connection including CGNAT. Free to start.
Get Your UDP Tunnel Free →