A VPN creates an encrypted tunnel between two points over an otherwise untrusted network, making traffic through it behave as if both ends were on the same private network. WireGuard is the modern default for this on Linux - a small, auditable codebase (merged directly into the kernel since 5.6) in place of the much larger attack surface of older protocols like OpenVPN or IPsec.

How it actually works at the network level

WireGuard creates a new virtual network interface (wg0, typically), the same category of thing as enp3s0 or lo covered in Networking Basics - traffic routed through that interface gets encrypted and sent to a peer, which decrypts it and either consumes it locally or routes it onward. WireGuard deliberately avoids the "client/server" language other VPNs use: every endpoint is a peer with the same configuration shape, even if one happens to act as a always-on relay for several others in practice.

Keys, not certificates

WireGuard uses simple public/private keypairs - no certificate authority, no PKI infrastructure to manage, conceptually much closer to SSH keys than to a TLS setup:

wg genkey | tee privatekey | wg pubkey > publickey

Each peer generates its own keypair once, shares its public key with the peers it wants to connect to, and keeps its private key secret - the same asymmetric-trust model as SSH's authorized_keys.

A minimal two-peer config

On peer A (/etc/wireguard/wg0.conf):

[Interface]
PrivateKey = <A's private key>
Address = 10.0.0.1/24
ListenPort = 51820

[Peer]
PublicKey = <B's public key>
Endpoint = 203.0.113.5:51820
AllowedIPs = 10.0.0.2/32

On peer B, the mirror image:

[Interface]
PrivateKey = <B's private key>
Address = 10.0.0.2/24

[Peer]
PublicKey = <A's public key>
Endpoint = 198.51.100.9:51820
AllowedIPs = 10.0.0.1/32

AllowedIPs does double duty: it's both a routing rule (traffic to this range goes to this peer) and a firewall rule (only packets appearing to originate from this range, cryptographically verified, are accepted from this peer). Setting AllowedIPs = 0.0.0.0/0 routes all of that peer's traffic through the tunnel - the "full tunnel" VPN pattern, as opposed to routing only specific subnets.

Bringing the tunnel up

sudo wg-quick up wg0
sudo wg-quick down wg0
sudo systemctl enable --now wg-quick@wg0    # persist across reboots
sudo wg show
interface: wg0
  public key: <...>
  listening port: 51820

peer: <B's public key>
  endpoint: 203.0.113.5:51820
  allowed ips: 10.0.0.2/32
  latest handshake: 42 seconds ago
  transfer: 1.21 MiB received, 892.44 KiB sent

A recent "latest handshake" confirms the tunnel is actually active and exchanging keys correctly, not just configured.

Firewall and NAT

The listening peer needs its ListenPort (UDP, not TCP) reachable through any firewall in front of it:

sudo ufw allow 51820/udp

See Firewalls: iptables and nftables. A peer behind NAT with no port forwarding can still initiate a connection outbound to a peer with a public address - it just can't be the one waiting to receive the first packet, the same constraint that shapes most peer-to-peer networking.

Compared to OpenVPN and IPsec

Both older protocols remain widely deployed and are more configurable in some respects (OpenVPN in particular supports certificate-based multi-user setups more directly), but come with substantially larger codebases and more configuration surface for something to be misconfigured or exploited. For a straightforward site-to-site or personal remote-access tunnel, WireGuard's small config and kernel-level performance make it the default recommendation today unless there's a specific reason to reach for the alternatives.