How to Set Up a WireGuard VPN Server on Ubuntu

Written by: Bagus Facsi Aginsa
Published at: 09 May 2026


Managing a remote team, running services in the cloud, or simply wanting a secure way to access your home lab from anywhere — these are exactly the situations where a VPN earns its place. But not all VPNs are created equal. OpenVPN has been the standard for years, but its setup complexity and performance overhead push many engineers toward something newer: WireGuard.

In this tutorial, you will set up a fully functional WireGuard VPN server on Ubuntu. By the end, you will have a server that accepts connections from peers (clients), routes their traffic through a private tunnel, and optionally forwards all internet traffic through your server.


What is WireGuard?

WireGuard is a modern, open-source VPN protocol built directly into the Linux kernel (since 5.6). Compared to OpenVPN or IPsec, it is:

  • Faster — a lean codebase (~4,000 lines vs OpenVPN’s ~70,000) means less overhead
  • Simpler to configure — no certificates or complex PKI; everything is based on public/private key pairs
  • More secure — uses state-of-the-art cryptography (Curve25519, ChaCha20, Poly1305) with sensible defaults; no algorithm negotiation means fewer attack surfaces
  • Always on — roaming between networks (Wi-Fi to 4G) reconnects instantly because the session is stateless

WireGuard works by creating a virtual network interface (wg0 by default). Each participant in the VPN — server or client — is called a peer. Each peer has a private key and shares its public key with the others. Traffic between peers is encrypted using those keys, and routing rules determine what traffic flows through the tunnel.


Prerequisites

Before starting, make sure you have:

  1. Two Ubuntu machines — one acts as the VPN server (ideally a VPS with a public IP), and one is your client (laptop, desktop, or another server). Both should run Ubuntu 20.04 or 22.04.
  2. Root or sudo access on both machines.
  3. A public IP address on the server, or at least a port forwarded through your router if it is behind NAT.
  4. Basic Linux familiarity — editing files with nano or vim, running commands with sudo.

In the examples below:

  • Server public IP: 203.0.113.1
  • VPN subnet: 10.0.0.0/24
  • Server VPN IP: 10.0.0.1
  • Client VPN IP: 10.0.0.2
  • WireGuard port: 51820 (UDP)

Step 1 — Install WireGuard on the Server

SSH into your server and install WireGuard. On Ubuntu 20.04 and 22.04, it is available in the default repositories.

sudo apt update
sudo apt install -y wireguard

Verify the installation:

wg --version

You should see output like wireguard-tools v1.0.20210914 - https://git.zx2c4.com/wireguard-tools/.


Step 2 — Generate the Server Key Pair

WireGuard uses asymmetric key pairs. You generate a private key, then derive the public key from it. The private key never leaves the machine; you share only the public key with peers.

wg genkey | sudo tee /etc/wireguard/server_private.key | wg pubkey | sudo tee /etc/wireguard/server_public.key

Lock down the private key so only root can read it:

sudo chmod 600 /etc/wireguard/server_private.key

Read both keys — you will need them in the next step:

sudo cat /etc/wireguard/server_private.key
sudo cat /etc/wireguard/server_public.key

Step 3 — Configure the WireGuard Interface on the Server

Create the main configuration file at /etc/wireguard/wg0.conf. Replace <SERVER_PRIVATE_KEY> with the content of server_private.key.

sudo nano /etc/wireguard/wg0.conf

Paste the following:

[Interface]
Address = 10.0.0.1/24
ListenPort = 51820
PrivateKey = <SERVER_PRIVATE_KEY>

# Enable IP forwarding and NAT for internet traffic routing
PostUp = iptables -A FORWARD -i wg0 -j ACCEPT; iptables -A FORWARD -o wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i wg0 -j ACCEPT; iptables -D FORWARD -o wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE

A few notes on the fields:

  • Address — the VPN IP address this interface will use, with the subnet mask for the VPN network.
  • ListenPort — the UDP port WireGuard listens on. Ensure this port is open in your firewall.
  • PostUp / PostDown — iptables rules that run when the interface comes up or goes down. The MASQUERADE rule is what makes internet traffic forwarding work: it rewrites the source IP of outbound packets to the server’s public IP so replies know where to come back to.

Note on eth0: Replace eth0 with your actual outbound network interface name. Check it with ip route get 8.8.8.8 | awk '{print $5; exit}'.

Lock down the config file:

sudo chmod 600 /etc/wireguard/wg0.conf

Step 4 — Enable IP Forwarding on the Server

By default, Linux does not forward packets between interfaces. You need to enable this so the server can route traffic from the VPN tunnel to the internet.

sudo nano /etc/sysctl.conf

Find the line #net.ipv4.ip_forward=1 and uncomment it (remove the #):

net.ipv4.ip_forward=1

Apply the change immediately without rebooting:

sudo sysctl -p

Confirm it is active:

sysctl net.ipv4.ip_forward
# Expected output: net.ipv4.ip_forward = 1

Step 5 — Open the Firewall Port

Allow WireGuard’s UDP port through UFW. If you are not using UFW, apply the equivalent rule in your cloud provider’s security group or iptables.

sudo ufw allow 51820/udp
sudo ufw status

Step 6 — Start the WireGuard Interface

Bring up the interface and enable it to start on boot:

sudo systemctl enable wg-quick@wg0
sudo systemctl start wg-quick@wg0

Check that it is running:

sudo wg show

Output should show the wg0 interface with its public key and listen port.


Step 7 — Set Up the Client

Now switch to your client machine (laptop or second server). Install WireGuard the same way:

sudo apt update
sudo apt install -y wireguard

Generate a key pair for the client:

wg genkey | sudo tee /etc/wireguard/client_private.key | wg pubkey | sudo tee /etc/wireguard/client_public.key
sudo chmod 600 /etc/wireguard/client_private.key

Read the client’s public key — you will need it on the server side:

sudo cat /etc/wireguard/client_public.key

Step 8 — Add the Client as a Peer on the Server

Go back to your server and open /etc/wireguard/wg0.conf. Append a [Peer] section for the client. Replace <CLIENT_PUBLIC_KEY> with the key you just generated.

sudo nano /etc/wireguard/wg0.conf

Add to the bottom of the file:

[Peer]
PublicKey = <CLIENT_PUBLIC_KEY>
AllowedIPs = 10.0.0.2/32
  • AllowedIPs = 10.0.0.2/32 — tells the server that packets destined for 10.0.0.2 should go to this peer, and that packets received from this peer must claim to come from 10.0.0.2. The /32 means this is a single host, not a network.

Reload the WireGuard configuration without dropping existing connections:

sudo wg syncconf wg0 <(sudo wg-quick strip wg0)

Or simply restart the service:

sudo systemctl restart wg-quick@wg0

Step 9 — Configure the Client Interface

Back on the client, create /etc/wireguard/wg0.conf. Replace the placeholders:

  • <CLIENT_PRIVATE_KEY> — content of /etc/wireguard/client_private.key
  • <SERVER_PUBLIC_KEY> — content of /etc/wireguard/server_public.key
  • 203.0.113.1 — your server’s real public IP
sudo nano /etc/wireguard/wg0.conf
[Interface]
Address = 10.0.0.2/24
PrivateKey = <CLIENT_PRIVATE_KEY>
DNS = 1.1.1.1

[Peer]
PublicKey = <SERVER_PUBLIC_KEY>
Endpoint = 203.0.113.1:51820
AllowedIPs = 0.0.0.0/0
PersistentKeepalive = 25

Key fields explained:

  • DNS = 1.1.1.1 — DNS server to use while connected. Without this, DNS queries might bypass the tunnel.
  • Endpoint — the server’s public IP and port. This is how the client knows where to send the initial handshake.
  • AllowedIPs = 0.0.0.0/0 — routes all traffic through the VPN tunnel. If you only want to route VPN subnet traffic (split tunneling), use 10.0.0.0/24 instead.
  • PersistentKeepalive = 25 — sends a keepalive packet every 25 seconds. This keeps the connection alive through NAT and firewalls that would otherwise drop the UDP session.

Set correct permissions:

sudo chmod 600 /etc/wireguard/wg0.conf

Step 10 — Connect and Test

Bring up the VPN on the client:

sudo wg-quick up wg0

Verify the handshake happened:

sudo wg show

You should see a [Peer] entry on the client with latest handshake showing a recent timestamp and bytes transferred. If you see (none) under latest handshake, the peers have not yet exchanged traffic — double-check the endpoint IP and port.

Test connectivity to the server’s VPN IP:

ping 10.0.0.1

If you configured AllowedIPs = 0.0.0.0/0, all your internet traffic now flows through the server. Verify your public IP has changed:

curl ifconfig.me

You should see 203.0.113.1 (the server’s IP), not your client’s original IP.

To enable the client VPN on boot:

sudo systemctl enable wg-quick@wg0

To disconnect:

sudo wg-quick down wg0

Common Mistakes and Troubleshooting

Handshake never completes

The most common cause is a firewall blocking UDP port 51820 on the server. Check:

sudo ufw status
# or check cloud provider security group rules

Also verify the server is actually listening:

sudo ss -ulnp | grep 51820

RTNETLINK answers: Operation not permitted when bringing up wg0

This often means the kernel module failed to load. Check:

sudo modprobe wireguard
dmesg | tail -20

On some minimal cloud images, you may need to install linux-modules-extra-$(uname -r).

Traffic is routed through the tunnel but DNS leaks

If you set AllowedIPs = 0.0.0.0/0 but did not set a DNS in the client config, your DNS queries may still go through your ISP. Add DNS = 1.1.1.1 to the [Interface] section of your client config and restart the tunnel.

Client can reach the server’s VPN IP but not the internet

This means IP forwarding or the MASQUERADE rule is not active on the server. Confirm:

sysctl net.ipv4.ip_forward
sudo iptables -t nat -L POSTROUTING -n -v

If the MASQUERADE rule is missing, the PostUp in your server config may reference the wrong interface name. Check with:

ip route get 8.8.8.8

And update eth0 in wg0.conf to match the real interface (often ens3, enp0s3, or eth0).

wg syncconf says “interface not found”

The interface is not up. Run sudo wg-quick up wg0 first, or use sudo systemctl start wg-quick@wg0.


Best Practices

Keep private keys private. Never paste a private key into a chat, ticket, or version control system. If a key is compromised, regenerate the pair and update the peer entry immediately.

Use unique IPs for each peer. Assign each client a distinct /32 address in the AllowedIPs field on the server side. Overlapping AllowedIPs across peers will cause unpredictable routing.

Restrict AllowedIPs for internal-only peers. If a peer (e.g., a backend server) only needs access to the VPN subnet and not the internet, set AllowedIPs = 10.0.0.0/24 in its client config instead of 0.0.0.0/0. This is split tunneling — only VPN-bound traffic goes through the tunnel.

Automate peer management for scale. For many peers, managing flat config files becomes painful. Tools like wg-easy or netmaker provide a web UI and API on top of WireGuard.

Monitor the tunnel health. The wg show command gives you per-peer transfer bytes and last handshake time. If latest handshake is older than 3 minutes, the peer has likely disconnected. You can wrap this in a cron job or monitoring script to alert on stale tunnels.

Use separate keys per device. If a user has a laptop and a phone, give each device its own key pair and [Peer] entry. This way, if one device is lost, you can revoke just that peer without affecting others — simply remove its [Peer] block from the server config and run sudo wg syncconf wg0 <(sudo wg-quick strip wg0).


Conclusion

You now have a working WireGuard VPN server on Ubuntu. Here is what you accomplished:

  • Installed WireGuard and generated key pairs on both server and client
  • Configured the server interface with IP forwarding and NAT for full traffic routing
  • Added a client peer and connected the tunnel
  • Verified the connection with a handshake check and public IP test

From here, you can expand the setup by adding more peers (repeat Steps 7–9 for each new client), exploring split tunneling to route only specific traffic through the VPN, or deploying WireGuard on mobile clients using the official WireGuard iOS/Android app — the peer configuration is identical, and both apps support scanning a QR code generated from the client config file with qrencode.

For production deployments, consider pairing WireGuard with a configuration management tool like Ansible to automate peer provisioning across a fleet of machines.