WireGuard VPN on Linux: Setup and Configuration

I spent years using OpenVPN for internal VPNs and I was always mildly frustrated by it. The configuration files are long, the certificates require careful management, the performance is decent but not great, and debugging connection issues is painful. Then I tried WireGuard and everything got simpler.

WireGuard is modern, fast, and has a remarkably clean configuration. It’s been in the Linux kernel since 5.6, so on any reasonably current system, it just works.

How WireGuard Is Different

WireGuard uses a different model than OpenVPN or IPsec. Instead of a client-server model with CA certificates, authentication, and negotiation, WireGuard is peer-to-peer. Each peer has a public/private key pair, and peers whitelist each other by exchanging public keys.

There’s no certificate management, no certificate authority, no revocation — just public key exchange. It’s similar in concept to SSH authorized_keys.

The protocol is UDP-only. If UDP is blocked on your network, WireGuard won’t work. In most cases this isn’t a problem.

Installing WireGuard

# Ubuntu 20.04+
apt install wireguard

# Debian 11+
apt install wireguard

# CentOS/RHEL 8+
dnf install wireguard-tools

On Ubuntu 18.04 you need the backport:

add-apt-repository ppa:wireguard/wireguard
apt install wireguard

Generating Keys

For each peer (server and each client):

wg genkey | tee privatekey | wg pubkey > publickey
chmod 600 privatekey

Store private keys securely — if you share a private key, anyone who has it can impersonate that peer. Public keys are meant to be shared.

Server Configuration

I’ll set up a server at 10.0.0.1/24 listening on UDP 51820. Edit /etc/wireguard/wg0.conf:

[Interface]
# The server's private key
PrivateKey = <server-private-key>

# The server's VPN IP
Address = 10.0.0.1/24

# UDP port WireGuard listens on
ListenPort = 51820

# Enable IP forwarding when the interface comes up
PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -A FORWARD -o %i -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -D FORWARD -o %i -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE

# One section per client peer
[Peer]
# Client's public key
PublicKey = <client-public-key>

# This client gets 10.0.0.2
AllowedIPs = 10.0.0.2/32

[Peer]
PublicKey = <client2-public-key>
AllowedIPs = 10.0.0.3/32

Replace eth0 in PostUp/PostDown with your actual network interface (ip addr to check).

Enable IP forwarding permanently:

echo "net.ipv4.ip_forward = 1" >> /etc/sysctl.d/99-wireguard.conf
sysctl -p /etc/sysctl.d/99-wireguard.conf

Open the firewall port:

ufw allow 51820/udp

Start and enable:

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

Or manually:

wg-quick up wg0
wg-quick down wg0

Client Configuration

On the client machine, create /etc/wireguard/wg0.conf:

[Interface]
# Client's private key
PrivateKey = <client-private-key>

# Client's VPN IP (must match AllowedIPs on server for this client)
Address = 10.0.0.2/24

# Optional: use the VPN's DNS
DNS = 10.0.0.1

[Peer]
# Server's public key
PublicKey = <server-public-key>

# Server's public IP and port
Endpoint = server.example.com:51820

# Route all traffic through VPN, or just specific subnets
# All traffic:
AllowedIPs = 0.0.0.0/0, ::/0
# Just VPN subnet:
# AllowedIPs = 10.0.0.0/24

# Keep-alive for NAT traversal (useful behind NAT)
PersistentKeepalive = 25

Start it:

wg-quick up wg0

Checking Status

wg show

Output shows each peer, their public key, endpoint, allowed IPs, and the last handshake time. If a peer shows “never” for last handshake, the connection isn’t established.

wg showconf wg0   # Shows the running config

Ping through the VPN to test:

ping 10.0.0.1    # From client, ping server VPN IP

Split Tunneling vs. Full Tunnel

If AllowedIPs = 0.0.0.0/0 on the client, all traffic routes through the VPN. This is a “full tunnel”.

For split tunneling (only route specific networks through VPN):

AllowedIPs = 10.0.0.0/24, 192.168.100.0/24

Only traffic destined for those subnets goes through WireGuard. Everything else goes through the normal internet connection. This is useful for corporate setups where you need access to internal resources but want to keep general browsing local.

Adding and Removing Peers

You don’t need to restart the interface to add peers. On a running interface:

# Add a peer
wg set wg0 peer <public-key> allowed-ips 10.0.0.10/32

# Remove a peer
wg set wg0 peer <public-key> remove

To make changes persistent, edit wg0.conf and reload:

wg addconf wg0 <(wg-quick strip wg0)

Or just wg-quick down wg0 && wg-quick up wg0 if a brief interruption is acceptable.

Key Rotation

There’s no automatic key rotation built into WireGuard. When I rotate keys, I:

  1. Generate new key pair on the client
  2. Update the server config with the new public key
  3. Remove the old peer from the server
  4. Update the client config with the new private key

The old peer entry on the server should be removed promptly — it won’t connect anymore once the client has a new key, but it’s cleaner to remove it.

Preshared Keys (Optional Extra Security)

WireGuard supports an optional preshared key per peer pair. This adds a layer of symmetric encryption on top of the asymmetric key exchange:

# Generate preshared key
wg genpsk > preshared.key

Add to both server and client configs under [Peer]:

[Peer]
PublicKey = ...
PresharedKey = <preshared-key>
AllowedIPs = 10.0.0.2/32

This provides post-quantum resistance — even if asymmetric encryption is broken in the future, the symmetric preshared key would need to be compromised too. For most setups it’s optional, but it’s a good practice for sensitive infrastructure.

Performance

WireGuard is fast. On a modern server, it can push multi-gigabit throughput because the crypto (ChaCha20-Poly1305 and Curve25519) is efficient and the kernel module avoids userspace overhead.

For comparison: OpenVPN typically saturates at a few hundred Mbps on a single core. WireGuard can push 1–2+ Gbps depending on hardware.

For site-to-site VPN links or connecting remote offices, this difference actually matters. For personal VPN use, it’s fast enough that you don’t notice the VPN overhead at all.

Troubleshooting

No handshake — check that the server’s UDP port 51820 is reachable from the client. Check firewall rules on both sides. Check that the public keys match (easy to mix up).

Connected but can’t route — verify IP forwarding is enabled on the server (cat /proc/sys/net/ipv4/ip_forward should return 1). Check that the iptables masquerade rule in PostUp ran correctly (iptables -t nat -L -n -v).

DNS not resolving through VPN — if you set DNS in the client config, wg-quick should configure it. Check resolv.conf or use resolvctl status to see current DNS settings.

Dropped connection on mobile — add PersistentKeepalive = 25 on the client. Mobile networks kill UDP connections if no traffic flows for a while; keepalive packets prevent this.

WireGuard has become my go-to for everything from personal VPNs to production site-to-site links. It’s simple enough that I understand exactly what’s happening, and reliable enough that I don’t think about it once it’s running.

Scroll to Top