How to Set Up WireGuard VPN on Linux

How to Set Up WireGuard VPN on Linux

Tested on: Ubuntu 26.04 LTS · Debian 12 · Arch Linux · Fedora 44 — Last updated: June 2026

WireGuard is a modern VPN protocol built directly into the Linux kernel since version 5.6. It outperforms OpenVPN and IPsec in connection speed, CPU overhead, and auditability — the entire implementation is roughly 4,000 lines of code. This guide walks through a complete server-and-client setup on Linux, with coverage of split tunneling, kill switches, mobile QR codes, and multi-peer management.

Contents
  1. Prerequisites
  2. How WireGuard Works
  3. Step 1 — Install WireGuard on the Server
  4. Step 2 — Configure the Server
    1. Generate Server Key Pair
    2. Identify Your Public Network Interface
    3. Write the Server Configuration
    4. Enable IP Forwarding and Start WireGuard
  5. Step 3 — Connect a Linux Client
    1. Generate Client Keys
    2. Register the Client as a Peer on the Server
    3. Create the Client Configuration
  6. Connect Windows, macOS, iOS, and Android
    1. Generate a QR Code for Mobile
  7. Split Tunneling
  8. Kill Switch
    1. Further Reading

Prerequisites

  • A Linux VPS or server with a public IP (Ubuntu 26.04, Debian 12, or Fedora 44 recommended)
  • Root or sudo access on both server and client machines
  • Linux kernel 5.6 or later on the server (uname -r to check); older kernels need the wireguard-dkms package
  • UDP port 51820 open on your server's firewall and any upstream security groups (AWS, GCP, Hetzner, etc.)
  • Basic familiarity with systemctl, iptables, and editing files with sudo

How WireGuard Works

WireGuard creates a virtual network interface — typically wg0 — on each machine. Every peer has an asymmetric key pair. You explicitly list which public keys are allowed to communicate, and what IP ranges they're permitted to route. There's no certificate authority, no TLS handshake, and no persistent connection state. If a peer goes silent, the interface just waits. When packets arrive with a valid authenticated key, the tunnel is live again within milliseconds.

Key concepts you'll use throughout this guide:

  • Interface: The wg0 virtual adapter on each machine, with its own IP address and private key
  • Peer: Any remote WireGuard endpoint you allow to connect to your interface
  • AllowedIPs: The IP ranges a peer is permitted to send and receive. On clients, setting 0.0.0.0/0 routes all traffic through the VPN (full tunnel). Narrower ranges enable split tunneling.
  • Endpoint: The public IP:port of a peer — only the server needs this set in the client config; the server learns the client's endpoint dynamically
  • PersistentKeepalive: Sends a keepalive packet every N seconds, necessary for clients behind NAT

Traffic is encrypted with ChaCha20-Poly1305 and authenticated with a combination of Curve25519 key exchange and BLAKE2s hashing. The cryptographic design is fixed — there are no negotiable cipher suites and no downgrade attacks.

Step 1 — Install WireGuard on the Server

# Ubuntu / Debian — wireguard-tools includes wg and wg-quick
sudo apt update && sudo apt install wireguard -y

# Fedora
sudo dnf install wireguard-tools -y

# Arch Linux
sudo pacman -S wireguard-tools

# Verify the kernel module is available
lsmod | grep wireguard

# If missing on older kernels (< 5.6), load it manually
sudo modprobe wireguard

# Confirm the wg tool is present
wg --version

On Ubuntu 26.04 and Debian 12 with a stock kernel, the module is built in — lsmod may show nothing until the interface is brought up, which is normal.

Step 2 — Configure the Server

Generate Server Key Pair

sudo mkdir -p /etc/wireguard
cd /etc/wireguard

# Generate private key and derive public key in one pipeline
wg genkey | sudo tee server_private_key | wg pubkey | sudo tee server_public_key

# Lock down permissions — the private key must never be world-readable
sudo chmod 600 /etc/wireguard/server_private_key

# Print both keys — you'll need the public key later when configuring clients
sudo cat /etc/wireguard/server_private_key
sudo cat /etc/wireguard/server_public_key

Identify Your Public Network Interface

# Find the interface name used for outbound traffic
ip route | grep default
# Example output:
default via 65.21.10.1 dev eth0 proto static

Note the interface name — eth0, ens3, enp1s0, etc. You'll substitute it into the NAT rules below. Hetzner and many cloud providers use eth0; some use ens3.

Write the Server Configuration

SERVER_PRIVATE=$(sudo cat /etc/wireguard/server_private_key)
WAN_IF=$(ip route | grep default | awk '{print $5}')

sudo tee /etc/wireguard/wg0.conf << EOF
[Interface]
PrivateKey = ${SERVER_PRIVATE}
Address = 10.8.0.1/24
ListenPort = 51820

# NAT: route client traffic out through the server's WAN interface
PostUp   = iptables -A FORWARD -i wg0 -j ACCEPT; iptables -A FORWARD -o wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o ${WAN_IF} -j MASQUERADE
PostDown = iptables -D FORWARD -i wg0 -j ACCEPT; iptables -D FORWARD -o wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o ${WAN_IF} -j MASQUERADE

# Peers will be appended below
EOF

sudo chmod 600 /etc/wireguard/wg0.conf

The PostUp and PostDown rules fire when wg-quick brings the interface up and down. They enable IP masquerading so that clients routed through the server appear to originate from its public IP.

Enable IP Forwarding and Start WireGuard

# Make IP forwarding persistent 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

# Enable and start the WireGuard interface
sudo systemctl enable --now wg-quick@wg0

# Verify it's up
sudo wg show
sudo systemctl status wg-quick@wg0
# Expected output from wg show:
interface: wg0
  public key: abc123...
  private key: (hidden)
  listening port: 51820
# Open the WireGuard port in UFW if it's active
sudo ufw allow 51820/udp
sudo ufw reload

Step 3 — Connect a Linux Client

Generate Client Keys

# Run these on the CLIENT machine
sudo apt install wireguard -y    # Debian/Ubuntu; adjust for your distro

wg genkey | tee client_private_key | wg pubkey > client_public_key

cat client_private_key    # keep this — goes in the client config
cat client_public_key     # this goes on the server as a [Peer] entry

Register the Client as a Peer on the Server

# Back on the SERVER — append a [Peer] block
# Replace CLIENT_PUBLIC_KEY_HERE with the actual key from the previous step
sudo tee -a /etc/wireguard/wg0.conf << 'EOF'

[Peer]
# laptop-alice
PublicKey = CLIENT_PUBLIC_KEY_HERE
AllowedIPs = 10.8.0.2/32
EOF

# Hot-reload the peer list without restarting the tunnel
sudo wg addconf wg0 <(sudo wg-quick strip wg0)

Alternatively, restart cleanly: sudo systemctl restart wg-quick@wg0. Existing sessions will drop briefly.

Create the Client Configuration

# On the CLIENT — replace placeholders with real values
sudo tee /etc/wireguard/wg0.conf << 'EOF'
[Interface]
PrivateKey = CLIENT_PRIVATE_KEY_HERE
Address = 10.8.0.2/32
DNS = 1.1.1.1, 1.0.0.1

[Peer]
PublicKey = SERVER_PUBLIC_KEY_HERE
Endpoint = YOUR_SERVER_IP:51820
AllowedIPs = 0.0.0.0/0, ::/0
PersistentKeepalive = 25
EOF

sudo chmod 600 /etc/wireguard/wg0.conf
# Bring the tunnel up
sudo wg-quick up wg0

# Confirm the handshake completed
sudo wg show

# Verify traffic exits through the VPN
curl -s ifconfig.me    # should return your server's public IP

# Tear down when done
sudo wg-quick down wg0

# Start automatically at boot
sudo systemctl enable wg-quick@wg0

Connect Windows, macOS, iOS, and Android

The WireGuard app is available on every major platform. The configuration format is identical to the Linux config — copy the same [Interface] and [Peer] block into the app's import dialog.

  • Windows: Download the installer from wireguard.com/install. Click Import tunnel(s) from file and select the .conf file.
  • macOS: App Store → WireGuard (by WireGuard Development Team) → Import from file.
  • iOS / Android: Use the QR code method below — it's faster than transferring a config file.

Generate a QR Code for Mobile

Create a per-device config file on the server (generate a new key pair for each mobile device — never reuse keys between devices), then encode it as a QR code the mobile app can scan directly.

# On the server — generate keys for a mobile device
wg genkey | sudo tee /etc/wireguard/phone_private | wg pubkey | sudo tee /etc/wireguard/phone_public
sudo chmod 600 /etc/wireguard/phone_private

# Add the phone as a peer (use 10.8.0.3 — each client needs a unique IP)
sudo tee -a /etc/wireguard/wg0.conf << EOF

[Peer]
# phone-alice
PublicKey = $(sudo cat /etc/wireguard/phone_public)
AllowedIPs = 10.8.0.3/32
EOF

sudo systemctl restart wg-quick@wg0

# Create the client config for the phone
sudo tee /etc/wireguard/phone-alice.conf << EOF
[Interface]
PrivateKey = $(sudo cat /etc/wireguard/phone_private)
Address = 10.8.0.3/32
DNS = 1.1.1.1

[Peer]
PublicKey = $(sudo cat /etc/wireguard/server_public_key)
Endpoint = YOUR_SERVER_IP:51820
AllowedIPs = 0.0.0.0/0, ::/0
PersistentKeepalive = 25
EOF

# Install qrencode and display the QR code in the terminal
sudo apt install qrencode -y
sudo qrencode -t ansiutf8 < /etc/wireguard/phone-alice.conf

Open the WireGuard app on your phone, tap the + button, select Scan from QR code, and point the camera at your terminal. The tunnel configuration is imported instantly. Delete the phone-alice.conf file from the server after scanning — the private key should not persist there.

Split Tunneling

Full-tunnel mode (AllowedIPs = 0.0.0.0/0) routes every packet through the VPN. Split tunneling routes only specific prefixes through the tunnel and lets everything else go out the local interface. This is useful for accessing internal services without slowing down general browsing.

# In the client's [Peer] section:

# Only route VPN subnet traffic — access internal services, nothing else
AllowedIPs = 10.8.0.0/24

# Access VPN subnet and a remote office LAN
AllowedIPs = 10.8.0.0/24, 192.168.100.0/24

# Everything EXCEPT the local subnet (advanced — use a CIDR calculator)
# wireguard.how/tools/allowedips is a reliable AllowedIPs calculator

When using split tunneling, remove the DNS line from [Interface] or set it to your internal DNS server — otherwise your system DNS changes every time the tunnel comes up.

Kill Switch

A kill switch drops all non-VPN traffic if the WireGuard interface goes down, preventing IP leaks. Add these rules to the client's [Interface] section:

[Interface]
PrivateKey = CLIENT_PRIVATE_KEY_HERE
Address = 10.8.0.2/32
DNS = 1.1.1.1
PostUp = iptables -I OUTPUT ! -o wg0 -m mark ! --mark $(wg show wg0 fwmark) -m addrtype ! --dst-type LOCAL -j REJECT && ip6tables -I OUTPUT ! -o wg0 -m mark ! --mark $(wg show wg0 fwmark) -m addrtype ! --dst-type LOCAL -j REJECT
PreDown = iptables -D OUTPUT ! -o wg0 -m mark ! --mark $(wg show wg0 fwmark) -m addrtype ! --dst-type LOCAL -j REJECT && ip6tables -D OUTPUT ! -o wg0 -m mark ! --mark $(wg show wg0 fwmark) -m addrtype


Go up

This site uses cookies for analytics and advertising (Google AdSense). By continuing to browse, you accept our use of cookies. Learn more