wireguard vpn server setup guide for linux
2024-06-17
Recently, I decided to set up a WireGuard VPN server for personal use. I found that existing guides about this subject are all lacking in some way. The server setup tutorials I found all go through the commands and configurations needed very throughly, but don’t explain why things work. Meanwhile, WireGuard’s Conceptual Overview focuses on the protocol itself and how it is different from others.
So, this post is a summary of my mental model of how WireGuard works, plus a tutorial for setting up the server. I assume that you have some knowledge of networking, but aren’t familiar with WireGuard. I also assume Linux knowledge. This guide should work generally, but I tested it on Arch Linux.
CIDR notation:
In this post, if you see an
/24
at the end of an IP address, that’s CIDR notation.192.168.0.0/24
could be written as “any address that fits the pattern192.168.0.*
”. Because there are 32 bits in an IPv4 address, the/24
at the end means that the first 24 bits (the first three bytes192.168.0
) are fixed, and the remaining 8 bits can be anything. Also, a/32
pattern matches only one address.
what is a vpn?
A VPN (virtual private network) is often advertised to the average user as an encrypted tunnel for their Internet connection. Mostly, this is useful for changing your IP address.
However, this is not the full picture of what a VPN is. In corporate networks, there are many important services that can’t be exposed publicly, which is why they’re only available on an internal, private network. Some off-site users, especially remote workers, need to access the internal network, but from the outside Internet. This is where a VPN can be useful: users can virtually be on the private network, as if they were on site. Using a VPN is more secure than publicly exposing services, as authentication allows fine-grained control over who can access the network.
For someone like me, VPNs are useful because I self-host private services. Normally, I would need to expose them to the public Internet to access them. Instead, I can access them solely through the local network or the VPN, preventing strangers from even seeing the login pages of my services. This reduces the attack surface and makes things more secure.
wireguard setup
WireGuard is a relatively recent protocol for implementing VPNs. It’s shiny and new, and also has a slimmer codebase than older, more established protocols.
Let’s go through the process of setting up a VPN to access an internal network.
The setup for tunneling all of a device’s Internet traffic is similar, and I will explain it too later.
We will have a Client device, and a Server device.
The Server is in the internal network (let’s say in the 192.168.0.0/24
subnet),
and the Client is outside of it.
There is also a publicly accessible domain vpn.example.com
that resolves to the Server.
To recap, here is a diagram of what we’re trying to do:
╭────────────╮
│ VPN Client │
╰─────┬──────╯
│ ╭────────────────────────────────────────────────╮
│ │ the private network (192.168.0.0/24) │
│ │ ╭───────────────────╮ │
╭───────┴─────────╮ │ │ VPN Server │ │
│ public internet ├────────┼──────┤ (vpn.example.com) ├────── ⋯ other hosts │
╰─────────────────╯ │ ╰─┬───────────────┬─╯ │
│ ╭──────┴──────╮ ╭────┴────────╮ │
│ │ machine 1 │ │ machine 2 │ │
│ │ 192.168.0.4 │ │ 192.168.0.7 │ │
│ ╰─────────────╯ ╰─────────────╯ │
│ │
╰────────────────────────────────────────────────╯
On Linux, one of the quicker ways to set up WireGuard is through wg-quick
,
which is an utility that takes a config file and sets up WireGuard with it.
In Arch Linux, this comes in the wireguard-tools
package:
# pacman -S wireguard-tools
wg-quick
stores config files under /etc/wireguard/
.
Each config file has the name [name].conf
,
where the file name is used as the interface name.
These are the same network interfaces as wlan0
or eth0
;
WireGuard typically uses names like wg0
.
server configuration
An important thing to understand about WireGuard is that it makes no distinction of server or client:
every device is a peer.
To authenticate with each other, each peer has a private key,
and a list of public keys of the peers it talks to.
This is similar to how SSH’s authorized_keys
works.
To generate both the private key and the public key at once, use this command:
$ wg genkey | tee /dev/tty | wg pubkey
WDax5fhKcKwdeuiYjgi4w/34ig2aZuDjmLHYnWUtfGc=
pJApgMHuIvMMsApTNXA3MMq+82nQ30XuVbAk9jsBNRs=
The private key will be on the first line, and the public key on the second. Keep these keys around, as they will be useful later.
Let’s now see the config file for the Server:
# SERVER config
# /etc/wireguard/wg0.conf
[Interface]
Address = 10.0.0.1/32
# REPLACE THIS!
PrivateKey = [private key]
ListenPort = 51820
# make sure to replace eth0 by the appropriate interface
PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
[Peer]
PublicKey = xZGlY8HIJt+rhGfbDK/T2xq0LQoR3kL6tGGVijaRBDI=
AllowedIPs = 10.0.0.2/32
First, let’s look at the [Interface]
section, which contains information about the Server.
It has the private key, and also the UDP port that WireGuard listens on
(remember to open this in the firewall).
The address marked here is the Server’s address.
Its subnet is completely different from the LAN the Server is actually on (192.168.0.0/24
).
This is because we’re creating a brand new private network (10.0.0.0/24
) inside the VPN connection,
where our Client and Server will coexist.
Note:
10.0.0.0/8
,172.16.0.0/12
and192.168.0.0/24
are all entirely reserved for private subnets, and you will not see them on the open Internet. To avoid collisions, the new VPN subnet should be different from the real LAN subnets for all peers. OpenVPN recommends obscure subnets like10.66.77.0/24
, which are equivalent to the middle of nowhere in IP address space.
There is also the PostUp
and PostDown
fields.
These are commands run in bash
after starting and stopping the VPN Server.
I’m not going to go into the details, but I’ll explain in general what they mean:
iptables -A
means to add a firewall rule when starting the VPN, and-D
is to delete the rule when the VPN stops.- The
%i
variable is part ofwg-quick
, and it expands to the VPN interface name (e.g.wg0
). - These rules in general allow the Server to forward packets, while masquerading. Essentially, just as a device can access the Internet through a router, the Client accesses the internal network through the Server. To do this, the Server will act like a router and perform NAT.
-o eth0
means the internal network is accessed over theeth0
interface.
Meanwhile, in the [Peer]
section, we write the Client’s public key,
which allows it to talk to the Server.
Our Client has an address of 10.0.0.2
, but
instead of an Address
field, we use AllowedIPs
.
These are the IP addresses that can be routed to and from this peer.
Here are some examples to clarify what that means:
- Let’s say the Server wants to send a packet to
10.0.0.2
. WireGuard sees that peerxZGlY...
(the Client) has this IP in its allowlist, so the Server sends the packet to the Client. - The Server wants to send a packet to
archlinux.org (95.217.163.246)
. This IP is not in the peer’s allowlist, so it will not route the packet to the Client. - The Client sends a packet to the Server, with the source being its IP address
10.0.0.2
. The Server sees that this IP is in the allowlist, so it accepts the packet from the Client. - The Client, who is now evil, decides to send a packet to the Server but impersonating another IP address:
10.0.0.69
. This IP not being in the Server’s allowlist, it rejects the packet.
In general, you can see that the AllowedIPs
field is what determines where packets can and can’t go.
By setting AllowedIPs = 10.0.0.2
, the server knows that the Client only has control over packets directed to or from that address.
It is not allowed packets to or from any other IP.
This concept of pairing the allowlist with public keys to manage packet routing is called cryptokey routing in WireGuard.
Also,
by default, Linux disables IP forwarding. To enable it, edit /etc/sysctl.conf
and add/uncomment the line
net.ipv4.ip_forward = 1
and run
# sysctl -p
to load the new configuration. If your VPN server is on the public internet, be sure to have sane firewall rules before doing this.
Note: If you use UFW as a firewall like me, note that it has its own
sysctl.conf
, which lives at/etc/ufw/sysctl.conf
. This will override the regularsysctl
if you follow the instructions above. To prevent it from erasing your changes, uncomment the relevant line:# /etc/ufw/sysctl.conf # Uncomment this to allow this host to route packets between interfaces net/ipv4/ip_forward=1
client configuration
Let’s now examine the Client’s configuration file.
If the client is running Linux, it will also be in /etc/wireguard/*.conf
,
However, the wg-quick
configuration format can be read by many other clients,
like the WireGuard Android app.
Anyways, here is the configuration file:
# CLIENT config
# wg0.conf
[Interface]
Address = 10.0.0.2/32
# REPLACE THIS!
PrivateKey = [private key]
# be careful not to use CIDR notation here
DNS = 10.0.0.1
[Peer]
PublicKey = M/HD4qJYi1RlMH/K9xQ12yW6Cu62LuGasyZhfnVsbUE=
# do not forget the port here
Endpoint = vpn.example.com:51820
AllowedIPs = 192.168.0.0/24
You’ll see this configuration is very similar to the Server’s, which is natural as WireGuard devices are all peers. There are a few differences, though.
First, there is a DNS
field, which can be used to prevent DNS leaks.
This only works if the VPN server provides a DNS server too.
Otherwise, set it to some other DNS server, or remove the line.
There is also the Endpoint
field.
This marks the Server’s public address on the open Internet.
The Endpoint
field is omitted in the Server configuration,
as it is implicit:
the Server will find out the endpoint IP when the Client reaches out to it for the first time.
Note: technically, the
Endpoint
field not always strictly necessary. IfSaveConfig
is enabled in the[Interface]
section, and either the Server or Client changes IP address while connected, roaming allows WireGuard to keep working and updateEndpoint
to the new address in the config file. I haven’t tested this, though.
Most importantly, we also have the AllowedIPs
for the server.
Here, it’s an entire subnet, and not just a single address.
What this means is that if the Client wants to send a packet to a device on the internal network,
say 192.168.0.4
, WireGuard matches the IP to the subnet 192.168.0.0/24
, takes the packet and routes it through to the server,
which then forwards these packets to the machine at 192.168.0.4
in the internal network.
If the machine then replies with a packet, WireGuard sees it is from the 192.168.0.0/24
subnet, then routes it back to the Client.
Using the VPN, the Client will be able to communicate with machines in the internal network as if it were there.
running the vpn
wg-quick
To start/stop the VPN for both Client and Server (if they run Linux), you can use
# wg-quick up wg0
# wg-quick down wg0
where wg0
should be the name of your configuration file.
Alternatively, there is a SystemD service you can use:
# systemctl start wg-quick@wg0
# systemctl stop wg-quick@wg0
or, to have WireGuard start at boot:
# systemctl enable wg-quick@wg0
# systemctl disable wg-quick@wg0
To diagnose issues with WireGuard, running wg show
can be useful:
# wg show
interface: wg0
public key: [snip]
private key: (hidden)
listening port: 49595
peer: [snip]
endpoint: [snip]:51820
allowed ips: 0.0.0.0/0
latest handshake: 57 seconds ago
transfer: 4.83 GiB received, 261.16 MiB sent
persistent keepalive: every 25 seconds
This can show that there is no handshake at all between peers, and thus that there is no connection.
networkmanager
There is also the option of using NetworkManager,
which is neat if you already use it to manage your existing network connections.
To do this, save your configuration file, and then import it through nmcli
:
$ nmcli con import type wireguard file wg0.conf
Then, you can manage the connection as usual through nmcli
:
$ nmcli con down wg0
$ nmcli con up wg0
$ nmcli con edit wg0
mobile devices
On mobile devices, WireGuard apps can also use these configuration files:
You can securely transfer the config files in a normal way,
but I recommend using QR codes because it’s way simpler.
Install the qrencode
package,
and do this:
$ cat wg0.conf | qrencode -t ansiutf8
█████████████████████████████████████
█████████████████████████████████████
████ ▄▄▄▄▄ █▄▄▄ ▀▀▀▄▀▄█▀██ ▄▄▄▄▄ ████
████ █ █ ██▄▀ █ ██▀▀▄▄▀█ █ █ ████
████ █▄▄▄█ ██▀▄ ▄▀▄█▄▄ ▄▀█ █▄▄▄█ ████
████▄▄▄▄▄▄▄█ ▀▄█ ▀▄▀ █▄█▄█▄▄▄▄▄▄▄████
████ ▀ ▀▄▀█▄▀█▄▄▄▄█ ▀ ▄▀▄▀▀█▀▀▄████
████▀█▄ ▀█▄ ▄██▄█ ▄ ▀▀▀▀▄ ▀▄█▀████
████▄█▄▄█▄▄█▀▄ █▀▄ ▀█▀▄█ █ ▀▀▀▀▀ ████
████▀ ▀█ █▄▄▄█▀█▀▀ █▄█▄▄▄▄ █ █ ████
████▀▄▀▀▄▀▄██ ▄██ ▄▀█ ███▀▄▀▀█████
████ ██▀▄▄█▀▀█▀▄ ▄▄ █▀▄█▀▄▀█ ▀█▄████
████▄▄▄▄█▄▄█▀ ▀▄▀ █ ▀ ▄█ ▄▄▄ ▄▄█████
████ ▄▄▄▄▄ ██▀▄ ▀ █ ▄ █ █▄█ ▄█▀█████
████ █ █ █ ▄ ▄▄ ▀ ▀█▀▄▄▄▄▄▄▄██ ████
████ █▄▄▄█ █▀ ▄▄█▄▀█▀▄█ █ ▀ ▄ ████
████▄▄▄▄▄▄▄█▄▄████▄▄▄▄█▄▄▄▄▄█▄███████
█████████████████████████████████████
█████████████████████████████████████
and the QR code will display in your terminal.
On your phone, select the option to add a connection via QR code,
and scan it.
You can pipe any data to qrencode
this way to generate a code,
and it also creates PNG files if you select that format.
encrypted internet tunnel
I mentioned earlier that to the average user, a VPN is an encrypted tunnel they can run their Internet traffic through. The topology is
Client -> VPN Server -> public internet -> website
which makes it look to the website that the traffic originates from the VPN server. This is actually very similar to our internal network set from before, which boils down to
Client -> VPN Server -> public internet -> internal network
To achieve the Internet tunnel topology, we just need to modify the client configuration:
[Peer]
PublicKey = M/HD4qJYi1RlMH/K9xQ12yW6Cu62LuGasyZhfnVsbUE=
Endpoint = vpn.example.com:51820
- AllowedIPs = 192.168.0.0/24
+ AllowedIPs = 0.0.0.0/0
This [Peer]
section refers to the Server.
We modify AllowedIPs
so that it includes all possible IPs
(0.0.0.0/0
),
rather than just the internal subnet’s IPs.
With this, every time the Client tries to communicate on the Internet,
for instance it requests archlinux.org (95.217.163.246)
,
WireGuard will match that IP to the 0.0.0.0/0
(all IPs) mask,
and route the packet to the Server.
Then, the Server will forward that packet to archlinux.org
.
Thus, we’ve made a VPN that works just like NordVPN or ProtonVPN, and all the others.
further reading
This guide distills my knowledge with the network topologies I use, i.e. connecting to an internal network, and connecting to the Internet via WireGuard. However, that’s of course not the only way you can use a VPN. Here are some resources that were useful to me:
- man pages
wg-quick(8)
andwg(8)
- Pro Custodibus: WireGuard
- WireGuard website
Thank you for reading this article!