429 lines
18 KiB
Markdown
429 lines
18 KiB
Markdown
# 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_](https://www.wireguard.com/#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](https://en.m.wikipedia.org/wiki/Classless_Inter-Domain_Routing).
|
|
> `192.168.0.0/24` could be written as "any address that fits the pattern
|
|
> `192.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 bytes `192.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` and `192.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](https://openvpn.net/community-resources/numbering-private-subnets/) obscure subnets like `10.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 of `wg-quick`, and it expands to the VPN interface name (e.g. `wg0`).
|
|
- These rules in general allow the Server to _forward_ packets, while [_masquerading_](https://askubuntu.com/a/1295626).
|
|
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](https://en.wikipedia.org/wiki/Network_address_translation).
|
|
- `-o eth0` means the internal network is accessed over the `eth0` 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 peer `xZGlY...` (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_](https://www.wireguard.com/#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](https://wiki.archlinux.org/title/Uncomplicated_Firewall) as a firewall like me, note that it has its own `sysctl.conf`, which lives at `/etc/ufw/sysctl.conf`.
|
|
> This will override the regular `sysctl` 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](https://en.m.wikipedia.org/wiki/DNS_leak).
|
|
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.
|
|
> If `SaveConfig` is enabled in the `[Interface]` section, and either the Server or Client changes IP address
|
|
> while connected, [roaming](https://www.wireguard.com/#built-in-roaming) allows WireGuard to keep working
|
|
> and update `Endpoint` 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](https://wiki.archlinux.org/title/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:
|
|
|
|
- F-Droid: [WG Tunnel](https://f-droid.org/packages/com.zaneschepke.wireguardautotunnel/)
|
|
- Google Play: [WireGuard](https://play.google.com/store/apps/details?id=com.wireguard.android)
|
|
- App Store: [WireGuard](https://apps.apple.com/us/app/wireguard/id1441195209)
|
|
|
|
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)` and `wg(8)`
|
|
- [Pro Custodibus: WireGuard](https://docs.procustodibus.com/guide/wireguard/)
|
|
- [WireGuard website](https://www.wireguard.com/)
|
|
|
|
Thank you for reading this article!
|