Skip to content

How WireGuard works

Cryptokey routing, the wg0 network interface, roaming between addresses, and living inside containers.

WireGuard wraps IP packets inside UDP and sends them to a peer. That sentence is almost the whole design. What makes it interesting is what it refuses to do: there is no key distribution mechanism, no configuration push, no negotiation about which cryptography to use. Those problems are real, but WireGuard treats them as somebody else’s layer, which is why it stays small.

The model to hold in your head is SSH. Two machines already know each other’s public keys. Nothing else needs arranging before traffic flows.

WireGuard appears as an ordinary network interface — wg0, wg1, and so on, sitting alongside eth0 and wlan0 in every tool you already use. Addresses go on it with ip address, routes go through it with ip route, and your firewall sees it the way it sees anything else. Only the WireGuard-specific parts — keys, peers, endpoints — need the wg tool.

This is a bigger deal than it sounds. It means WireGuard inherits your operating system’s networking stack instead of reimplementing one, and it means your existing monitoring, routing and firewalling knowledge transfers unchanged.

Every interface holds one private key and a list of peers. Every peer has a public key and a list of tunnel addresses that key is allowed to use.

That list does double duty, and understanding which job it is doing in which direction is the thing worth learning.

Sending. The interface is handed a packet and looks at its destination address. It searches the peers’ allowed-address lists for the one that covers that destination, encrypts the packet to that peer’s public key, and sends it to the last endpoint that peer was seen at. No match means the packet is dropped — there is no default route through a tunnel that isn’t configured for it.

Receiving. A UDP packet arrives. If it decrypts and authenticates, WireGuard now knows which peer’s key produced it. It then reads the source address of the plaintext packet inside and asks whether that key is permitted to claim that address. If yes, the packet goes up the stack. If not, it is dropped.

So the same list behaves as a routing table on the way out and an access control list on the way in. A peer cannot spoof another peer’s address, because the address is checked against the key that actually did the cryptography.

A machine acting as a hub, with two peers:

[Interface]
PrivateKey = <hub private key>
ListenPort = 51820
[Peer]
# laptop — one address, and only that address
PublicKey = <laptop public key>
AllowedIPs = 10.7.0.2/32
[Peer]
# a branch office router, which fronts a whole subnet
PublicKey = <branch public key>
AllowedIPs = 10.7.0.3/32, 172.20.8.0/24

The laptop’s own configuration is shorter, because it has exactly one peer:

[Interface]
PrivateKey = <laptop private key>
[Peer]
PublicKey = <hub public key>
Endpoint = hub.example.org:51820
AllowedIPs = 0.0.0.0/0

AllowedIPs = 0.0.0.0/0 on the laptop means two things at once, both following from the rules above: send everything through this peer, and accept packets from it regardless of what source address they carry. That is how you route all your traffic over the tunnel — not a special mode, just the wildcard case of the same table.

The branch entry shows why the list is a list. That peer may speak for itself at 10.7.0.3 and for everything behind it in 172.20.8.0/24, and the hub will accept both from that key and nothing else.

Notice that the laptop’s configuration names an endpoint for the hub, but the hub’s configuration names no endpoint for the laptop. It doesn’t need one.

When a packet authenticates, WireGuard records where it came from and uses that address for replies. So a peer that moves — wifi to mobile data, one café to the next, a new address from DHCP — simply keeps working. The next authenticated packet from the new address updates the record. There is no reconnect, no session to re-establish, and nothing for you to configure.

This is also why WireGuard doesn’t need to distinguish clients from servers. The asymmetry in the example above is only that one side knows where to find the other first.

When neither side has traffic to send, WireGuard sends nothing at all. No keepalives, no heartbeat. That is excellent for a phone’s battery and awkward for NAT, because a stateful firewall that stops seeing packets eventually drops the mapping — and then nothing can reach you until you speak first.

Where that matters, a persistent keepalive fixes it, at the cost of the silence. See Install for the setting and the interval the project recommends.

The interface and the encrypted socket do not have to live in the same place. You can create a WireGuard interface inside a network namespace while the UDP socket that carries its traffic stays outside, in a different namespace.

That inversion is genuinely useful. A container can be given a network interface whose only route to the outside world is the tunnel, while the host retains the actual internet connection. The process inside cannot reach the network except through WireGuard, and it cannot turn that off, because the socket isn’t in its namespace to interfere with.

The protocol summary and the technical whitepaper on the official site are the authoritative descriptions, and the whitepaper is a genuine academic paper rather than marketing — see Cryptography and security for what it covers and where to find it.