Skip to content

TutorialsVPN & private DNS

How to set up a WireGuard VPN server on Ubuntu or Debian

Set up a WireGuard VPN server on Ubuntu or Debian with wg-quick and ufw: keys, IP forwarding, NAT, client configs with QR codes, more peers and troubleshooting.

  • Intermediate
  • 35 min read
  • Updated

Tested on: Ubuntu 24.04 LTS, Ubuntu 26.04 LTS, Debian 12, Debian 13

This guide is not available in your language yet, so it is shown in English.

On this page
  1. Prerequisites
  2. Step 1 — Install WireGuard
  3. Step 2 — Generate the keys
  4. Step 3 — Find the public network interface
  5. Step 4 — Write the server configuration
  6. Step 5 — Enable IP forwarding
  7. Step 6 — Add firewall, forwarding and NAT rules with ufw
  8. Step 7 — Start the tunnel with systemd
  9. Step 8 — Create the client configuration and QR code
  10. Step 9 — Add more peers
  11. Full or split tunnel
  12. Back up and restore
  13. Update WireGuard
  14. Troubleshooting
  15. There is no latest handshake in wg show
  16. The handshake works but there is no internet
  17. Websites do not resolve while connected
  18. Some sites hang or only load partly
  19. wg-quick@wg0 fails with Operation not supported
  20. Next steps

WireGuard is a modern VPN protocol that is built into the Linux kernel. It uses a small set of current cryptography, needs only one UDP port, and is configured with short text files, which makes it fast and easy to audit. With your own WireGuard server you can route your laptop and phone through a server you control on untrusted networks, or reach services on the server that you do not want to expose to the internet.

This guide sets up a WireGuard server with the distribution packages and wg-quick, the standard tool that ships with wireguard-tools. You generate keys with safe file permissions, write /etc/wireguard/wg0.conf, enable IP forwarding, add NAT and forwarding rules with ufw so clients can reach the internet, run the tunnel as the wg-quick@wg0 systemd service, and create client configurations that you can import as QR codes. Adding peers, full versus split tunnels, backups and troubleshooting are covered at the end. If you prefer a web interface for managing clients, see WireGuard with wg-easy instead.

Prerequisites

  • A server running Ubuntu 24.04 LTS, Ubuntu 26.04 LTS, Debian 12 or Debian 13. Their kernels include WireGuard, and the wireguard package is in the standard repositories. The steps work on a HyperDC Linux VPS, VDS or dedicated server with root access.
  • A non-root user with sudo rights and SSH key login: see Secure a new Linux server and Set up SSH keys.
  • ufw installed and enabled with SSH allowed (on Debian, install it with sudo apt install ufw).
  • The server's public IP address (this guide uses 203.0.113.10) and, if your provider has a network firewall in its control panel, UDP port 51820 allowed there too.
  • The WireGuard app on each client: the official apps for Windows, macOS, iOS and Android, or wireguard-tools on Linux.
ResourceMinimum (official)Suggested starting point
CPUNot published1 vCPU
RAMNot published512 MB to 1 GB
DiskNot publishedNo extra space beyond the OS
NetworkNot publishedEnough bandwidth for all clients that route through the server

The WireGuard project does not publish minimum requirements; the right-hand column is a conservative starting point. Bandwidth, not CPU or memory, is usually the limit for a personal or small-team VPN.

Step 1 — Install WireGuard

Install the tools and qrencode, which turns client configurations into QR codes for phones:

Bash
sudo apt update
sudo apt install wireguard qrencode
wg --version

wg --version prints the wireguard-tools version. The kernel module comes with the Ubuntu and Debian kernels, so nothing has to be compiled.

Step 2 — Generate the keys

Every WireGuard peer has a private key and a public key derived from it. Private keys must be readable only by root. WireGuard's quick start sets umask 077 before generating keys, so new files are created with mode 600. Create one key pair for the server and one for the first client, here called laptop:

Bash
sudo mkdir -p /etc/wireguard/clients
sudo chmod 700 /etc/wireguard /etc/wireguard/clients
sudo sh -c 'umask 077; wg genkey | tee /etc/wireguard/server.key | wg pubkey > /etc/wireguard/server.pub'
sudo sh -c 'umask 077; wg genkey | tee /etc/wireguard/clients/laptop.key | wg pubkey > /etc/wireguard/clients/laptop.pub'
sudo ls -l /etc/wireguard /etc/wireguard/clients

The listing shows the key files as -rw------- and owned by root. Never share a .key file; only .pub files are meant to be exchanged.

Step 3 — Find the public network interface

NAT and forwarding rules need the name of the interface that leads to the internet. It is often eth0, but can be ens3, enp1s0 or similar:

Bash
ip -o -4 route show to default

The output looks like default via 203.0.113.1 dev eth0 proto static. The word after dev is your interface name. This guide uses eth0; replace it everywhere below if yours is different.

Step 4 — Write the server configuration

Create /etc/wireguard/wg0.conf. The VPN uses the private network 10.8.0.0/24: the server is 10.8.0.1 and each client gets its own address. The command inserts the server's private key and the laptop's public key directly from the files:

Bash
sudo tee /etc/wireguard/wg0.conf > /dev/null <<EOF
[Interface]
Address = 10.8.0.1/24
ListenPort = 51820
PrivateKey = $(sudo cat /etc/wireguard/server.key)

[Peer]
# laptop
PublicKey = $(sudo cat /etc/wireguard/clients/laptop.pub)
AllowedIPs = 10.8.0.2/32
EOF
sudo chmod 600 /etc/wireguard/wg0.conf
  • Address and ListenPort are read by wg-quick; the interface will be called wg0 after the file name.
  • In a [Peer] section on the server, AllowedIPs is the client's own VPN address as a /32. The server only accepts packets from that peer with this source address and routes traffic for that address to it.
  • Pick a private range that does not clash with the networks your clients use at home or at work.

Step 5 — Enable IP forwarding

The server must forward packets between wg0 and the internet. Enable IPv4 forwarding permanently with a sysctl file:

Bash
echo "net.ipv4.ip_forward=1" | sudo tee /etc/sysctl.d/99-wireguard.conf
sudo sysctl --system
sysctl net.ipv4.ip_forward

The last command prints net.ipv4.ip_forward = 1.

Step 6 — Add firewall, forwarding and NAT rules with ufw

This guide uses ufw for everything, following the IP masquerading example in Ubuntu's ufw-framework documentation. Three things are needed: open the WireGuard port, allow forwarding from wg0 to the internet, and masquerade (NAT) the VPN addresses behind the server's public IP. Back up before.rules first, then append a nat section at the end of the file:

Bash
sudo ufw allow 51820/udp
sudo ufw route allow in on wg0 out on eth0
sudo cp /etc/ufw/before.rules /etc/ufw/before.rules.bak
sudo tee -a /etc/ufw/before.rules > /dev/null <<'EOF'

# NAT for WireGuard clients
*nat
:POSTROUTING ACCEPT [0:0]
-A POSTROUTING -s 10.8.0.0/24 -o eth0 -j MASQUERADE
COMMIT
EOF
sudo ufw reload
sudo iptables -t nat -S POSTROUTING
sudo ufw status verbose

The iptables output contains the MASQUERADE rule for 10.8.0.0/24, and ufw status verbose lists 51820/udp and an ALLOW FWD rule from wg0 to eth0. ufw's default policy for routed traffic stays deny, so only VPN traffic is forwarded.

Step 7 — Start the tunnel with systemd

wireguard-tools ships the [email protected] template, which runs wg-quick up for the named configuration at boot:

Bash
sudo systemctl enable --now wg-quick@wg0
sudo systemctl status wg-quick@wg0 --no-pager
sudo wg show
ip -brief address show wg0

systemctl status shows active (exited), which is normal for this one-shot unit. wg show lists the interface with its public key and listening port, and the laptop peer without a handshake yet.

Step 8 — Create the client configuration and QR code

Write the laptop's configuration into the clients folder. Replace 203.0.113.10 with your server's IP address or a host name that points to it:

Bash
sudo tee /etc/wireguard/clients/laptop.conf > /dev/null <<EOF
[Interface]
PrivateKey = $(sudo cat /etc/wireguard/clients/laptop.key)
Address = 10.8.0.2/24
DNS = 9.9.9.9, 149.112.112.112

[Peer]
PublicKey = $(sudo cat /etc/wireguard/server.pub)
Endpoint = 203.0.113.10:51820
AllowedIPs = 0.0.0.0/0, ::/0
PersistentKeepalive = 25
EOF
sudo chmod 600 /etc/wireguard/clients/laptop.conf
  • DNS sets the resolvers the client uses while connected. Any public resolver works; if you run AdGuard Home over this VPN, use 10.8.0.1 instead.
  • AllowedIPs = 0.0.0.0/0, ::/0 sends all traffic through the tunnel (see Full or split tunnel below).
  • PersistentKeepalive = 25 sends a small packet every 25 seconds, the interval WireGuard's quick start suggests for peers behind NAT or firewalls.

For a phone, show the configuration as a QR code in the terminal and scan it with the WireGuard app (Add tunnel, then scan from QR code):

Bash
sudo cat /etc/wireguard/clients/laptop.conf | qrencode -t ansiutf8

For a desktop, display the file with sudo cat /etc/wireguard/clients/laptop.conf and paste it into the app as a new empty tunnel, or save it on a Linux client as /etc/wireguard/wg0.conf and run sudo wg-quick up wg0. Connect, then run sudo wg show on the server: the peer now shows a latest handshake and growing transfer counters, and websites see the server's IP address as yours.

The configuration file holds the client's private key. Once the client is set up, you can delete laptop.key and laptop.conf from the server, or keep them only in your encrypted backups.

Step 9 — Add more peers

Each device gets its own key pair and its own address; never share one configuration between devices. To add a phone with the address 10.8.0.3, create its keys, append a [Peer] section and reload the service:

Bash
sudo sh -c 'umask 077; wg genkey | tee /etc/wireguard/clients/phone.key | wg pubkey > /etc/wireguard/clients/phone.pub'
sudo tee -a /etc/wireguard/wg0.conf > /dev/null <<EOF

[Peer]
# phone
PublicKey = $(sudo cat /etc/wireguard/clients/phone.pub)
AllowedIPs = 10.8.0.3/32
EOF
sudo systemctl reload wg-quick@wg0
sudo wg show

The reload action of the systemd unit applies the changed file with wg syncconf, which only changes what differs and does not interrupt connected peers. Then create phone.conf as in Step 8 with phone.key and Address = 10.8.0.3/24. To revoke a device, delete its [Peer] section and reload again.

Full or split tunnel

The client's AllowedIPs decides what goes through the VPN; the server configuration stays the same:

  • Full tunnel (0.0.0.0/0, ::/0): all traffic goes through the server, useful on public Wi-Fi. This guide only gives the tunnel IPv4 addresses, so listing ::/0 mainly stops IPv6 traffic from bypassing the VPN; sites that are reachable only over IPv6 will not load until you add IPv6 to the tunnel.
  • Split tunnel (10.8.0.0/24): only traffic to the VPN network uses the tunnel, for example to reach services that listen on 10.8.0.1. Everything else uses the client's normal connection, and you can drop the DNS line.

Back up and restore

The whole VPN is defined by a few files: /etc/wireguard (server key, wg0.conf and client files), the NAT section in /etc/ufw/before.rules and the sysctl file. Archive them and keep the archive private, because it contains private keys:

Bash
sudo mkdir -p /opt/backups
sudo tar -czf /opt/backups/wireguard-$(date +%F).tar.gz /etc/wireguard /etc/ufw/before.rules /etc/sysctl.d/99-wireguard.conf
sudo chmod 600 /opt/backups/wireguard-*.tar.gz

To restore on a new server, install the packages (Step 1), unpack the archive with sudo tar -xzf /opt/backups/wireguard-2026-10-09.tar.gz -C /, check the interface name in before.rules, run the two ufw commands from Step 6 and sudo sysctl --system, and start the service as in Step 7. The keys are unchanged, so clients keep working; only their Endpoint line needs editing if the server's IP address changed. Copy the archive off the server.

Update WireGuard

WireGuard is part of the kernel, and the tools come from the distribution, so normal system updates cover both:

Bash
sudo apt update
sudo apt upgrade
sudo reboot

Reboot after kernel updates; wg-quick@wg0 starts again by itself. Check with sudo wg show that the interface is back and clients reconnect.

Troubleshooting

There is no latest handshake in wg show

The client's packets do not reach the server or the keys do not match. Check that UDP 51820 is allowed in ufw and in your provider's firewall, that Endpoint has the right IP address and port, and that the keys are crossed correctly: the server's [Peer] holds the client's public key, and the client's [Peer] holds the server's public key. WireGuard stays silent on wrong keys by design, so there is no error message.

The handshake works but there is no internet

Forwarding or NAT is missing. Check sysctl net.ipv4.ip_forward (must be 1), sudo iptables -t nat -S POSTROUTING (must contain the MASQUERADE rule) and sudo ufw status verbose (must show the ALLOW FWD rule). The most common cause is a wrong interface name instead of eth0 in both places.

Websites do not resolve while connected

The client has no working DNS. Check the DNS line in the client configuration. On Linux clients, wg-quick applies it with resolvconf; if you see resolvconf: command not found, install a resolvconf implementation from your distribution or remove the DNS line and configure DNS another way.

Some sites hang or only load partly

This points to an MTU problem, typical on mobile networks, PPPoE lines or nested tunnels. wg-quick chooses the MTU automatically; set a lower value by adding MTU = 1280 to the client's [Interface] section and reconnect.

wg-quick@wg0 fails with Operation not supported

The kernel has no WireGuard support. Run sudo modprobe wireguard and look at the error. Standard Ubuntu and Debian kernels include the module, so this usually means a custom kernel or a container-based environment where kernel modules cannot be loaded.

Next steps

Frequently asked questions

Should I use plain WireGuard or wg-easy?

Plain WireGuard, as in this guide, has no web interface and the fewest moving parts; you manage peers in a text file. wg-easy runs WireGuard in a Docker container with a web UI for creating clients. Use one or the other on a server, not both on the same port.

Which port does WireGuard need?

Only the UDP port in ListenPort, 51820 in this guide. WireGuard does not use TCP. You can pick another UDP port, but then change it in the firewall rule and in every client Endpoint line too.

What is the difference between a full tunnel and a split tunnel?

It is decided by AllowedIPs in the client config. 0.0.0.0/0 and ::/0 send all traffic through the server (full tunnel). A private range such as 10.8.0.0/24 sends only traffic for the VPN network through it (split tunnel).

Should client keys be generated on the server?

It is convenient because you can show a QR code, but generating the key pair on the client device is more private, since the private key then never leaves it. The WireGuard apps can create keys; you only add the client public key to the server.

Does WireGuard keep connection logs?

WireGuard itself writes no connection logs. sudo wg show displays each peer's last endpoint, latest handshake and transfer counters while the interface is up, and that information is gone after a restart.

Sources

Generiraj lozinku

Please confirm