# 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.

Difficulty: Intermediate\
Tested on: Ubuntu 24.04 LTS, Ubuntu 26.04 LTS, Debian 12, Debian 13

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](/guides/install-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](/guides/secure-a-new-linux-server) and [Set up SSH keys](/guides/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.

| Resource | Minimum (official) | Suggested starting point |
|---|---|---|
| CPU | Not published | 1 vCPU |
| RAM | Not published | 512 MB to 1 GB |
| Disk | Not published | No extra space beyond the OS |
| Network | Not published | Enough 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.

> **Warning**
>
> Run the `tee -a` command only once; each run appends another `nat` section. If something goes wrong, restore the copy with `sudo cp /etc/ufw/before.rules.bak /etc/ufw/before.rules` and run `sudo ufw reload`.

## Step 7 — Start the tunnel with systemd

`wireguard-tools` ships the `wg-quick@.service` 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](/guides/install-adguard-home), 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

- Prefer a web interface for clients? Try [WireGuard with wg-easy](/guides/install-wg-easy).
- Block ads and trackers for all VPN clients with [AdGuard Home over WireGuard](/guides/install-adguard-home).
- Reach admin pages only through the VPN, for example the [Vaultwarden](/guides/install-vaultwarden) admin page.
- Compare servers for your own VPN on the [VPN server](/vpn-server) page.
- Read the [WireGuard quick start](https://www.wireguard.com/quickstart/) and the `wg-quick(8)` manual page for all options.

## 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.

---

Source: <https://hyperdc.com/guides/tutorials/wireguard-vpn-server>\
Updated: 2026-10-09
