Skip to content
VPNmine
SETUP GUIDES

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

Illustration of the Linux penguin and a terminal prompt, joined by a dashed tunnel line on a dark grid.

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:

  • AllowedIPs is both a routing table and an access control list. On the client it means "send these destinations through the tunnel", so 0.0.0.0/0, ::/0 is a full tunnel and anything narrower is a split tunnel.
  • DNS is not part of the protocol. wg-quick uses it to rewrite the system resolver while the interface is up, which needs resolvconf or systemd-resolved present.
  • PersistentKeepalive sends 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.
  • Endpoint is 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.

Sources

  1. WireGuard — quick start
  2. wg-quick(8) manual page
  3. Linux 5.6 changelog

Try it on the network this was written from

VPNmine is a free no-logs VPN running its own WireGuard servers, with native apps for Windows, macOS, Android, iOS, Android TV and Apple TV. No ads, no card.

Get VPNmine