How to set up WireGuard on Linux
Install the wireguard-tools package, put the configuration file in /etc/wireguard, and bring it up with wg-quick. Enable the matching systemd unit to make it persist across reboots. WireGuard has been in the mainline Linux kernel since version 5.6, so no external module is needed on any current distribution.
6 min read
WireGuard went into the mainline Linux kernel in version 5.6, released in March 2020. Every distribution shipping a kernel newer than that already has the module, so setup on Linux is not an installation problem at all — it is a configuration file and one command.
Install the tools
The kernel does the tunnelling; wireguard-tools provides wg and wg-quick,
which is the shell script that turns a config file into a working interface.
# Debian, Ubuntu
sudo apt install wireguard-tools
# Fedora
sudo dnf install wireguard-tools
# Arch
sudo pacman -S wireguard-tools
Check the module is present with modinfo wireguard. If it is missing you are
on a kernel older than 5.6, and the DKMS package wireguard-dkms covers that
case.
The configuration file
A client config is short. Put it at /etc/wireguard/vpnmine.conf and make sure
it is not world-readable, because it contains a private key.
[Interface]
PrivateKey = <your private key>
Address = 10.7.0.14/32
DNS = 1.1.1.1, 1.0.0.1
[Peer]
PublicKey = <server public key>
PresharedKey = <optional, if the provider issues one>
Endpoint = fra-1.example.net:51820
AllowedIPs = 0.0.0.0/0, ::/0
PersistentKeepalive = 25
Four of those lines carry all the behaviour worth understanding:
AllowedIPsis both a routing table and an access control list. On the client it means "send these destinations through the tunnel", so0.0.0.0/0, ::/0is a full tunnel and anything narrower is a split tunnel.DNSis not part of the protocol.wg-quickuses it to rewrite the system resolver while the interface is up, which needsresolvconforsystemd-resolvedpresent.PersistentKeepalivesends an empty packet every 25 seconds to hold a NAT mapping open. Needed behind NAT, harmless otherwise, and it is what lets a server reach a client that has not spoken first.Endpointis where the handshake goes. If it is a hostname, it is resolved once at interface bring-up, which matters when the tunnel is also what resolves names.
sudo chmod 600 /etc/wireguard/vpnmine.conf
Bring it up
sudo wg-quick up vpnmine
sudo wg show
sudo wg-quick down vpnmine
wg show is the diagnostic worth learning. It prints the latest handshake time
and the byte counters per peer. A recent handshake with rising counters means a
working tunnel; a handshake that never appears means the packets are not
reaching the server at all.
Make it survive a reboot
wg-quick ships a templated systemd unit:
sudo systemctl enable --now wg-quick@vpnmine
sudo systemctl status wg-quick@vpnmine
The unit name after the @ is the config filename without its extension. That
is the whole of the persistence story on any systemd distribution.
DNS, and the ordering trap
If the config sets DNS and the endpoint is a hostname, bring-up needs to
resolve that hostname before it changes the resolver. wg-quick handles that
correctly, but a network-manager profile that brings the tunnel up before the
network is fully configured can deadlock: the resolver is the tunnel, and the
tunnel needs the resolver.
The fix is an IP address in Endpoint rather than a hostname, at the cost of
having to update the file if the server moves.
Verify the result with the DNS leak test. If lookups still
reach your provider, resolvconf is probably not installed and wg-quick
silently skipped the DNS line — it warns, and the warning scrolls past.
MTU
wg-quick picks an MTU automatically, usually 1420. On PPPoE lines that is 8
bytes too many, and the symptom is nasty: small requests work, large transfers
hang. Set it explicitly in the [Interface] section:
MTU = 1412
1280 is the safe floor, because every IPv6 path must carry at least that.
A kill switch in two lines
WireGuard has no kill switch setting, but wg-quick runs arbitrary commands on
bring-up and tear-down, and AllowedIPs = 0.0.0.0/0 combined with a firewall
rule that rejects anything leaving the physical interface achieves it:
PostUp = iptables -I OUTPUT ! -o %i -m mark ! --mark $(wg show %i fwmark) -m addrtype ! --dst-type LOCAL -j REJECT
PreDown = iptables -D OUTPUT ! -o %i -m mark ! --mark $(wg show %i fwmark) -m addrtype ! --dst-type LOCAL -j REJECT
That is the rule from the project's own documentation. It rejects any packet not carrying the tunnel's firewall mark, so when the interface goes down nothing leaves.
Where to get a config
Providers issue them per device, because the private key in the file identifies one peer. VPNmine's are generated per device from the panel; the Linux page covers where to find them. Never share a config between machines: two peers with the same key will fight over the same tunnel address and both will behave erratically.
Reading wg show when something is wrong
The output is short and every line means something specific.
No latest handshake line at all. The handshake never completed. Packets
are not reaching the server, or the server is rejecting the key. Check that the
endpoint port is not blocked outbound — many corporate and campus networks drop
UDP entirely — and that the public key on the server matches the private key in
your file.
A handshake that is minutes old and not advancing. WireGuard renegotiates roughly every two minutes while traffic flows, and stops when nothing is being sent. An old handshake on an idle tunnel is normal. An old handshake while you are actively trying to load pages is not, and usually means return traffic is being dropped somewhere.
Transfer counters rising on send but not on receive. Your packets leave and nothing comes back. That is a server-side routing or firewall problem, or an MTU black hole if the small packets work and the large ones do not.
Routing tables, and what wg-quick actually did
wg-quick with a full-tunnel AllowedIPs does something subtler than adding a
default route. It creates a separate routing table, marks the tunnel's own
packets with a firewall mark so they are excluded from it, and adds a rule
sending everything else to that table. That is why the endpoint remains
reachable while all other traffic is captured.
ip rule show and ip route show table 51820 print the result. If you are
debugging why one destination behaves differently from the rest, those two
commands answer it faster than anything else.
Running more than one tunnel
Nothing stops several interfaces existing at once, each with its own config file
and its own wg-quick@ unit. Only one of them can carry the default route
sensibly, so the usual arrangement is one full tunnel and one or more narrow
ones with specific AllowedIPs for particular subnets.
Give each a distinct Address and a distinct FwMark if you set them by hand;
two tunnels sharing a mark will interfere with each other's routing rules in
ways that are tedious to diagnose.