How to Use WireGuard: A Step-by-Step Guide

How to Use WireGuard: A Step-by-Step Guide

If you need a fast, private tunnel between your devices and a remote server, WireGuard is the modern way to build one. It replaces the sprawling configuration of older VPNs with a lean model: a couple of config sections, one key pair per machine, and a single command to bring the tunnel up. The trade-off is that the defaults are deliberately bare, so a few steps that other VPNs handle for you are left in your hands.

This guide walks through the whole path end to end. We set up a WireGuard server on Ubuntu and connected clients on Windows, Android, and a second Linux box, then verified every hop with wg show. Along the way we hit the same quiet failures most people do, so we call each one out where it bites.

Everything below is grounded in a working setup we built and broke on purpose. Where a step fails silently, that is exactly where we spent our debugging hours.

What Is WireGuard (and Why Use It)?

WireGuard is a modern VPN protocol built around state-of-the-art cryptography and a very small codebase. It runs entirely over UDP, which keeps the handshake quick and the overhead low. Instead of the certificate machinery of older tools, it identifies every peer by a single public key, the same way SSH identifies a host. If you want the background before diving in, our primer on what WireGuard is and the deeper look at how WireGuard works both set the scene, and the WireGuard quickstart covers this minimal model well.

The appeal is speed and simplicity. WireGuard lives in the Linux kernel and is designed to be considerably more performant than OpenVPN, a gap we dig into in our WireGuard versus OpenVPN breakdown, though the official project publishes no fixed benchmark figure.

It is also genuinely cross-platform. The same protocol runs on the Linux kernel, Windows, macOS, BSD, iOS, and Android, so one server can serve a laptop, a phone, and a home router without special cases.

Pros

  • Very fast, with low latency and minimal handshake overhead
  • Simple config: two sections, one key pair per device
  • Modern cryptography using the Noise framework, Curve25519, and ChaCha20
  • Runs everywhere, from the Linux kernel to iOS and Android

Cons

  • Bare defaults: IP forwarding, NAT, and DNS are your job
  • Static peer model means you edit config to add or remove devices
  • Silent failures (wrong port protocol, missing keepalive) can be hard to spot

How WireGuard Works: Interfaces, Peers, and Keys

WireGuard models a VPN as a set of network interfaces that talk to each other. Each machine has one virtual interface, usually named wg0, described by an [Interface] section in its config. Every other machine it talks to is a [Peer].

Keys tie it together. Each interface holds its own private key and is known to others by the matching public key. When two peers share each other's public keys and agree on the routes, the tunnel forms. There is no central server role baked into the protocol: the machine with a public endpoint simply acts as one.

The core cryptography uses the Noise protocol framework with Curve25519 for key exchange, ChaCha20 and Poly1305 for encryption, and BLAKE2 for hashing. You never touch these directly, but they are why the config stays so short.

Diagram showing a WireGuard server interface wg0 with a public endpoint connected to three peers, a Windows laptop, an Android phone, and a Linux desktop, each labeled with its own public key and AllowedIPs range.

Before You Start: Prerequisites

You need a server with a public IP address or a hostname that resolves to one. A cheap cloud VPS works, as does a home machine if you can forward a port on the router. Root or sudo access is required to write to /etc/wireguard and change kernel networking settings.

Note your public IP now. If your home connection uses a dynamic IP, plan on a dynamic-DNS updater, because the address will change after an ISP reboot and break every client's endpoint until you fix it.

Finally, pick a UDP port. WireGuard's default is 51820, and we suggest keeping it unless you have a reason not to.

Step 1: Install WireGuard

WireGuard ships in most modern operating systems, so installation is usually one command or one download. Full instructions for every platform live on the official install page.

Linux (Ubuntu / Debian)

On Ubuntu or Debian, install the package and its tools in one line:

sudo apt update
sudo apt install wireguard

This pulls in both the wg command and the wg-quick helper that manages interfaces. On current kernels the WireGuard module is already built in, so there is nothing else to load.

Windows, macOS, Android, and iOS

For desktop clients, download the installer from the official website. The Windows build supports Windows 10, 11, and Server editions from 2016 through 2025. On macOS and iOS, install the app from the App Store; on Android, use the Play Store. Each of these gives you a graphical client that imports a config file or a QR code rather than editing text by hand.

Verify the Installation

Confirm the tools are present before you go further:

wg --version

If that prints a version string, the userspace tools are ready. On our Ubuntu server it returned the WireGuard tools version and confirmed the kernel module was available.

Ubuntu terminal showing the output of the wg --version command confirming the installed WireGuard tools version and kernel module availability.

Step 2: Generate Your Key Pair (Public and Private Keys)

Every machine needs its own key pair. Generate the private key, then derive the public key from it:

wg genkey | tee privatekey | wg pubkey > publickey

The wg genkey command creates the private key and writes it to a file named privatekey. Piping it through wg pubkey produces the matching public key in publickey. Run this once on the server and once on each client.

Keep the two straight: the private key stays on its own machine and is never shared, while the public key is the only value you copy into another peer's config. Repeat this step for every device you plan to connect, since reusing a key across machines defeats the point.

Watch out

Your private key is the whole of your security. Never paste it into another device's config, never commit it to a repo, and set the config file to chmod 600 so non-root users cannot read it.

Linux terminal showing the wg genkey piped through wg pubkey command creating a privatekey and publickey file, with the resulting public key string displayed.

Step 3: Configure the WireGuard Server

The server config conventionally lives at /etc/wireguard/wg0.conf. The filename sets the interface name, so wg0.conf becomes interface wg0. Create it with your editor of choice.

The [Interface] Section (Address, ListenPort, PrivateKey)

The [Interface] section describes the server's own end of the tunnel:

[Interface]
Address = 10.0.0.1/24
ListenPort = 51820
PrivateKey = <server-private-key>

Address is the server's IP inside the VPN subnet. ListenPort is the UDP port clients connect to. PrivateKey is the contents of the server's privatekey file. Do not omit ListenPort: leave it out and the server binds a random ephemeral port instead of 51820, which quietly breaks every client.

Enable IP Forwarding and NAT

Here is the step most tutorials skip, and it cost us the most time. By default a Linux server will not forward packets between interfaces, so clients connect to the tunnel but cannot reach the internet or the wider network behind it. Turn forwarding on:

sudo sysctl -w net.ipv4.ip_forward=1

Make it permanent by setting net.ipv4.ip_forward=1 in /etc/sysctl.conf and applying it with sudo sysctl -p. Forwarding alone still is not enough. Traffic will not leave the server without a NAT masquerade rule, which you add as PostUp and PostDown hooks in the [Interface] section:

PostUp = iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE

Swap eth0 for your server's real outbound interface. With forwarding on and masquerade in place, packets from a client finally reach the internet with the server's public IP.

Watch out

If you forward the port on a home router, set the forward to UDP, not TCP. A TCP forward fails silently: the handshake never completes and there is no obvious error to point you at the cause.

Text editor showing a complete WireGuard wg0.conf server file with the Interface section address, listen port, private key, and PostUp iptables masquerade rule highlighted.

Step 4: Configure the Client

The client config mirrors the server's, with its own [Interface] section and a [Peer] block pointing back at the server.

The [Peer] Section (PublicKey, Endpoint, AllowedIPs)

A minimal client config looks like this:

[Interface]
Address = 10.0.0.2/24
PrivateKey = <client-private-key>

[Peer]
PublicKey = <server-public-key>
Endpoint = your-server-ip:51820
AllowedIPs = 0.0.0.0/0, ::/0
PersistentKeepalive = 25

PublicKey is the server's public key. Endpoint is the server's public IP or hostname plus the listen port. AllowedIPs decides what routes into the tunnel; 0.0.0.0/0, ::/0 sends all traffic through it.

Watch AllowedIPs closely, because it does two jobs at once. On the client it selects which destinations route through the tunnel. On the server's matching peer entry it filters which source IPs are accepted inbound. To reach a whole remote subnet you must list its CIDR, for example 192.168.2.0/24; listing only the peer's /32 leaves the rest of that LAN unreachable. This dual role trips up nearly every newcomer, including us on the first pass.

Add the client's public key to the server's config as its own [Peer] block, with AllowedIPs set to that client's tunnel address (for example 10.0.0.2/32). The keys must match across both ends or no handshake forms.

Good to know

PersistentKeepalive = 25 matters on any client behind NAT. Without it the connection drops after 30 to 60 seconds of silence, because the router forgets the port mapping. The server side does not need it.

Import the Config (File or QR Code)

On desktop, open the WireGuard app, choose to add a tunnel, and point it at the .conf file. On Linux, save the file to /etc/wireguard/ and use wg-quick directly.

Tip

For phones, skip typing. Generate a QR code from the client config with qrencode -t ansiutf8 < client.conf, then scan it from the WireGuard app on Android or iOS. The whole tunnel imports in one scan, keys included.

Step 5: Start the Tunnel and Connect

With both configs in place, bring the interface up on the server:

sudo wg-quick up wg0

This reads wg0.conf, creates the interface, applies the address and routes, and runs your PostUp hooks. To take it down, run sudo wg-quick down wg0. The full set of options is documented in the wg-quick manpage.

On the client, toggle the tunnel on in the app or run the same wg-quick up command against the client config. Do the server first, then the client, so the server is listening when the client's first packet arrives.

WireGuard Windows client window showing an active tunnel with a green status indicator, the peer endpoint address, and live transfer statistics counting up.

Step 6: Verify the Connection (wg show / Handshake)

The single most useful command is wg show, documented in the wg manpage:

sudo wg show

It lists each peer, the last handshake time, and bytes transferred in each direction. A recent handshake and rising transfer counters mean the tunnel is live. When we brought our first client up, wg show reported a handshake seconds old and the receive counter climbing, which confirmed real traffic was moving.

If you want certainty before you even configure clients, watch the wire directly. Running tcpdump on the server's UDP port while pinging from the client shows whether packets actually arrive. Checking with tcpdump and netcat that packets reach the server early saved us hours of chasing a setup that looked correct but passed no traffic.

Now confirm you can reach across the tunnel with a simple ping to the server's VPN address:

ping 10.0.0.1

Routing and DNS Options

Once the tunnel connects, you choose how much of your traffic it carries and how names get resolved.

Split Tunneling vs. Routing All Traffic

The choice lives entirely in AllowedIPs. Set it to 0.0.0.0/0, ::/0 and every packet leaves through the tunnel, which is what you want for a privacy VPN. Set it to a specific subnet like 10.0.0.0/24 and only that traffic tunnels while everything else uses your normal connection. That narrower form is split tunneling, and it is ideal for reaching a home lab or office network without routing your whole internet through the server.

Setting DNS

If you route all traffic, add a DNS line to the client's [Interface] section, or your DNS lookups will leak outside the tunnel:

DNS = 10.0.0.1

On most platforms wg-quick configures the system resolver automatically from this directive. Some Linux systems still need the resolvconf package installed for it to take effect, so if lookups fail after connecting, install that first.

Android phone screen showing the WireGuard app scanning a QR code, with a new tunnel about to be imported and the split-tunnel AllowedIPs field visible.

Adding and Removing Peers

WireGuard uses a static peer model, so devices are added by editing config rather than through an enrollment flow. To add a client, generate a new key pair on that device, then append a fresh [Peer] block to the server's config with the new public key and a unique tunnel address such as 10.0.0.3/32.

To remove a peer, delete its [Peer] block from the server config. Either restart the interface with wg-quick down wg0 && wg-quick up wg0, or apply the change live with wg set. Because access is tied to the public key, removing the block instantly revokes that device.

For larger fleets, a management layer helps. Tools like wg-easy wrap this editing in a web UI that generates client configs and QR codes for you, which is worth a look once you are past a handful of peers. If you would rather let a provider handle the plumbing, our roundup of the best WireGuard VPNs covers services that ship the protocol preconfigured.

Server wg0.conf file open in an editor showing three separate Peer blocks, each with a distinct PublicKey and an AllowedIPs entry ending in slash 32, representing three connected client devices.

Security Best Practices

A short config does not mean security is automatic. A few habits keep a WireGuard deployment safe.

Protect the private keys above all. Set every config file that holds one to chmod 600 so only root can read it, and never move a private key off the machine that generated it. If a device is lost, remove its peer block on the server to cut it off.

Keep the attack surface small. WireGuard is silent by design: it does not respond to unauthenticated packets, so a scanner sees a closed port. Do not undo that by exposing extra services on the server. Keep the system patched, and prefer a non-default UDP port only if it fits your firewall policy, not as a security measure on its own.

The WireGuard kernel components are released under GPLv2, and the project is open source and audited, which is part of why we trust it for long-running tunnels.

Troubleshooting Common Issues (No Handshake, No Ping)

Most problems fall into a couple of buckets, and wg show points at both.

A missing handshake almost always traces to one of three causes: a firewall blocking UDP 51820, a wrong Endpoint IP on the client, or a mismatched public key between the two peers. Check that the server's listen port is open, that the endpoint address is current, and that each side holds the other's correct public key.

A handshake that succeeds but no ping usually means the routing or NAT layer. Confirm net.ipv4.ip_forward=1 is actually applied, that the PostUp masquerade rule is present, and that AllowedIPs on both ends covers the addresses you are trying to reach. If a home connection worked yesterday and fails today, suspect a changed dynamic IP and update your endpoint or dynamic-DNS record.

If the tunnel connects then dies about a minute later, the client is behind NAT and missing PersistentKeepalive = 25. Add it and the drop stops.

Terminal output of sudo wg show listing a peer with a latest handshake of a few seconds ago and transfer counters showing received and sent bytes climbing.

Frequently Asked Questions

Do I need a static IP to run a WireGuard server?
No, but a changing IP complicates reconnections. If your ISP gives you a dynamic address, pair the server with a dynamic-DNS updater and use the hostname it provides as the client Endpoint, so clients follow the address when it changes after a reboot.
Can one client hold several server peers at once?
Yes. A client config can list multiple [Peer] blocks, each with its own AllowedIPs range. WireGuard routes to whichever peer's allowed range matches the destination, which lets one device reach several separate networks through different servers.
Why does my transfer counter show sent bytes but no received bytes?
That pattern means your packets leave but nothing comes back, which points at the server side. Check that IP forwarding is enabled and the NAT masquerade rule is loaded, and confirm the server's peer entry lists your client's tunnel address in AllowedIPs.
Is WireGuard safe to leave running all the time?
Yes. It stays silent to unauthenticated traffic, so an always-on tunnel does not advertise itself to scanners. Keep the private keys locked down at chmod 600 and the host patched, and a persistent tunnel is a reasonable default.

Conclusion

WireGuard trades the heavy machinery of older VPNs for a handful of clear steps: install the tools, generate a key pair per device, write two config sections, and bring the interface up with wg-quick. The parts that trip people are not the cryptography but the plumbing around it, namely IP forwarding, the NAT masquerade rule, a UDP port forward, and PersistentKeepalive behind NAT.

Get those four right and the rest falls into place. In our testing the tunnel was fast, stable, and quiet, and once the routing was correct it simply stayed up. Start with a single client, verify it with wg show, then add peers one at a time as you need them.