WireGuard VPN Server Setup on Linux: NAT, Clients, Mobile
A WireGuard VPN server setup on Linux takes about ten minutes: install the package, generate a key pair, define a wg0 interface, enable IP forwarding, and add a NAT rule so clients can reach the internet through the server. This guide walks through each step on Ubuntu/Debian, then covers desktop and mobile clients, split tunneling, and the mistakes that cause most "connected but no traffic" problems.
All addresses below use documentation ranges. The server's public IP is 203.0.113.10, and the tunnel network is 10.8.0.0/24. Replace both with your own values.
What You Need
- A Linux server with a public IPv4 address and root or sudo access (a VDS works well; see below).
- Kernel 5.6 or newer, where WireGuard is built in. Ubuntu 22.04 and later and Debian 11 and later qualify. Older kernels can use the
wireguard-dkmspackage. - One UDP port open to the internet. We'll use 51820.
- A full virtual machine, not a restricted container. Some container platforms don't let you create network interfaces or load kernel modules.
Step 1: Install WireGuard
sudo apt update
sudo apt install -y wireguard qrencode
qrencode is optional, but it's the easiest way to onboard phones later.
Step 2: Generate the Server Keys
sudo -i
cd /etc/wireguard
umask 077
wg genkey | tee server.key | wg pubkey > server.pub
cat server.pub
The private key never leaves the server. The public key goes into each client config. The umask 077 keeps the key file readable only by root.
Step 3: Find Your Public Network Interface
NAT rules must reference the interface that faces the internet. Check its name:
ip -4 route show default
The output shows something like default via 203.0.113.1 dev eth0. Here the interface is eth0. On many systems it's ens3, ens192, or enp1s0. A wrong name here is the most common cause of a tunnel that connects but passes no traffic.
Step 4: Create the Server Config
Create /etc/wireguard/wg0.conf:
[Interface]
Address = 10.8.0.1/24
ListenPort = 51820
PrivateKey = <contents of server.key>
PostUp = iptables -A FORWARD -i wg0 -j ACCEPT; iptables -A FORWARD -o wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -s 10.8.0.0/24 -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i wg0 -j ACCEPT; iptables -D FORWARD -o wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -s 10.8.0.0/24 -o eth0 -j MASQUERADE
# Peers are added below
Lock down the file and keep the key out of shell history:
chmod 600 /etc/wireguard/wg0.conf
The PostUp and PostDown lines add and remove the forwarding and masquerade rules when the interface goes up or down, so nothing lingers after a stop.
If you use nftables natively, create the equivalent rule: a postrouting chain in a nat table with ip saddr 10.8.0.0/24 oifname "eth0" masquerade.
Step 5: Enable IP Forwarding
Without forwarding, the kernel drops packets that need to go from wg0 out through eth0.
echo "net.ipv4.ip_forward=1" | sudo tee /etc/sysctl.d/99-wireguard.conf
sudo sysctl --system
If you also want to route IPv6 through the tunnel, add net.ipv6.conf.all.forwarding=1 and give the tunnel a ULA or routed prefix. Don't hand out IPv6 addresses to clients unless the server can actually route them. A broken IPv6 path with ::/0 in the client's AllowedIPs causes slow, confusing failures.
Step 6: Open the Firewall and Start the Interface
With UFW:
sudo ufw allow 51820/udp
sudo ufw allow OpenSSH
sudo ufw enable
If UFW's default forward policy is DROP, the PostUp FORWARD rules above still apply, but check /etc/default/ufw if traffic stalls. Also open UDP 51820 in any provider-level or upstream firewall in front of the server.
Start it and enable it at boot:
sudo systemctl enable --now wg-quick@wg0
sudo wg show
Step 7: Create a Client Config
Generate keys for the client. Doing it on the server is fine for a small setup. For stricter hygiene, have each user generate their own key pair and send you only the public key.
cd /etc/wireguard
wg genkey | tee laptop.key | wg pubkey > laptop.pub
wg genpsk > laptop.psk
Add the peer to the server config:
[Peer]
# laptop
PublicKey = <contents of laptop.pub>
PresharedKey = <contents of laptop.psk>
AllowedIPs = 10.8.0.2/32
On the server, AllowedIPs means "which source addresses this peer may use." Give each client a single /32. Apply the change without dropping existing sessions:
sudo bash -c 'wg syncconf wg0 <(wg-quick strip wg0)'
The client config, laptop.conf:
[Interface]
PrivateKey = <contents of laptop.key>
Address = 10.8.0.2/32
DNS = 1.1.1.1
[Peer]
PublicKey = <contents of server.pub>
PresharedKey = <contents of laptop.psk>
Endpoint = 203.0.113.10:51820
AllowedIPs = 0.0.0.0/0
PersistentKeepalive = 25
On the client, AllowedIPs means "which destinations go into the tunnel." Setting it to 0.0.0.0/0 sends all IPv4 traffic through the VPN (full tunnel). The DNS line points at a resolver you trust; you can also run your own resolver on the server and use 10.8.0.1.
Split Tunneling
If you only want to reach a private network or a few servers, list those ranges instead:
AllowedIPs = 10.8.0.0/24, 198.51.100.0/24
Only traffic to those ranges uses the tunnel, and everything else goes out normally. Remove the DNS line, or point it at an internal resolver, so you don't push all DNS lookups through the VPN unintentionally.
Linux, Windows, and macOS Clients
- Linux: copy the file to
/etc/wireguard/wg0.conf, then runsudo wg-quick up wg0. Usesystemctl enable wg-quick@wg0for persistence. - Windows and macOS: install the official WireGuard app, choose "Import tunnel(s) from file," and activate it.
Mobile Clients (iOS and Android)
Create a config for the phone the same way, using a new key pair and the next free address, such as 10.8.0.3/32. Then render it as a QR code in the terminal:
qrencode -t ansiutf8 < /etc/wireguard/phone.conf
In the official WireGuard app for iOS or Android, tap Add, choose "Create from QR code," and scan it. Treat the QR code like a password, because it contains the private key. Don't paste it into chat tools or screenshots.
For mobile, keep PersistentKeepalive = 25. Phones sit behind carrier NAT, and without keepalives the mapping expires and inbound packets stop arriving. Both apps also offer an on-demand option, which can bring the tunnel up automatically on cellular or untrusted Wi-Fi.
Verify It Works
On the server:
sudo wg show
A working peer shows a recent latest handshake and growing transfer counters. From the client, check that your public IP is now the server's:
curl -4 https://ifconfig.me
Common Mistakes and Fixes
| Symptom | Likely cause | Fix |
|---|---|---|
| No handshake at all | UDP 51820 blocked, or wrong Endpoint or key | Run tcpdump -ni eth0 udp port 51820 on the server and check provider firewalls |
| Handshake works, no internet | Forwarding off or wrong NAT interface | Check sysctl net.ipv4.ip_forward and the -o eth0 name |
| Some sites hang, others load | MTU too high | Set MTU = 1380 in the client [Interface] |
| Phone drops after idle | NAT mapping expired | Set PersistentKeepalive = 25 |
| Two clients fight each other | Duplicate AllowedIPs or reused keys | Use a unique /32 and key pair per device |
| DNS leaks | DNS not set or not routed through the tunnel | Set DNS = and use 0.0.0.0/0 for full tunnel |
WireGuard's default MTU is 1420. That's right for most links, but PPPoE, LTE, and nested tunnels sometimes need a lower value.
Hardening Tips
- Keep SSH separate from the VPN, and don't lock yourself out. Test firewall changes with a second session open.
- Use a unique key pair per device, and remove a peer's block when a device is lost.
- Use a preshared key for an extra symmetric layer on top of the public-key exchange.
- Restrict what VPN clients can reach with FORWARD rules, for example allowing
wg0only to specific destinations. - Keep the kernel and
wireguard-toolsupdated.
Where to Run It
A WireGuard server needs a stable public IP, a reliable network path, and enough CPU for encryption. The load is small for a handful of users. A VDS is a good fit. Nixcon Group LLC runs its own network, AS40368, with locations in Izmir, Frankfurt, and Fremont, California. You can compare the paths from each location with the public looking glass before you choose where to deploy. Our VDS servers run on VMware vSphere, so you get a full Linux VM with a kernel that supports WireGuard.
If you'd rather not manage keys, firewalls, and updates yourself, see our VPN services.
Frequently asked questions
Is WireGuard faster than OpenVPN?
In most deployments WireGuard has lower overhead. It runs in the Linux kernel, uses a small modern cryptographic suite, and keeps the codebase compact. Real results depend on CPU, MTU, and the network path, so test on your own link.
Which port does WireGuard use?
There is no fixed port. UDP 51820 is the common default, and you can change ListenPort in wg0.conf. Open the same port in the server firewall and any upstream firewall.
Why does my WireGuard client connect but have no internet?
Usually IP forwarding is disabled, or the NAT masquerade rule names the wrong outbound interface. Check net.ipv4.ip_forward, confirm the interface with ip route, and make sure the FORWARD chain allows traffic from wg0.
How do I add a phone to my WireGuard server?
Generate a new key pair and assign an unused tunnel address, such as 10.8.0.3/32. Add it as a peer on the server and apply the change with wg syncconf. Build the client config, then show it as a QR code with qrencode and scan it in the WireGuard iOS or Android app.