VPN Basics with WireGuard
WireGuard is a modern, minimal VPN protocol built directly into the Linux kernel - a small config file and a pair of keys create an encrypted tunnel between two machines, or an entire private network, without the complexity of older VPN protocols.
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.