The Logo of WireGuard, under public domain

VPN with WireGuard

Notes published the
7 - 9 minutes to read, 1791 words

Here is another use case for an old PC: create your self-hosted VPN!

For me, a self-hosted VPN has two main use cases:

  • to be able to access my devices that are otherwise not accessible from the internet

  • to be able to access regionally restricted websites when I am temporarily somewhere else

Those are (more or less) my step-by-step notes for installing and configuring WireGuard as a VPN on a Debian machine.

Install Debian on the Raspberry

While this is a perfect job for an old PC, I used a Raspberry.

The main reason is that since I wanted to place the device with WireGuard in another country, I needed to set it up on a different device. The Raspberry, even if it does not have a lot of computational power, has enough resources for handling this task.

Thus the machine running WireGuard must be located somewhere else, and I needed to use a different machine from the one I’m already using.

To setup the raspberry, download the desired image, as described on the wiki:

wget https://raspi.debian.net/daily/raspi_${RPI_MODEL}_${DEBIAN_RELEASE}.img.xz.sha256
wget https://raspi.debian.net/daily/raspi_${RPI_MODEL}_${DEBIAN_RELEASE}.img.xz
sha256sum -c raspi_${RPI_MODEL}_${DEBIAN_RELEASE}.img.xz.sha256
# if sha256sum reports success...
xzcat raspi_${RPI_MODEL}_${DEBIAN_RELEASE}.img.xz | sudo dd of=${SD_CARD} bs=64k oflag=dsync status=progress

Insert the SD card in the Raspberry, attach it to a monitor, ethernet cable, and keyboard, and power it on.

Except for adding a normal user, nothing should be specific to the Raspberry.

From a shell with administrative rights:

# add a normal user, in this case named "urpi"
adduser urpi
usermod -aG sudo urpi
# install some useful packages for working remotely
apt update
apt install tmux sudo
# get ip address of the machine
ip address show

From my PC

# add entry in .ssh/config for the Raspberry, set up the ssh key, and so on
ssh rpi
# at this point, it is possible to detach the Raspberry from the monitor and keyboard
# just leave the power and ethernet cable and work from a more comfortable device through SSH
sudo tmux
# start upgrade process in one pane/window
apt upgrade

# change hostname from a different tmux pane
hostnamectl set-hostname rpi
# also edit value in /etc/hosts
# 127.0.1.1       rpi

# eventually do other tasks while updates are installed, it will take a while on the Raspberry

Some hardening

Contrary to my other other devices, this one will be directly accessible from the internet.

Thus it would be nice if "bad guys" are not simply able to connect to it.

Most GNU/Linux distributions are pretty robust in their default configuration, but there are some things that can be improved.

SSH

Enable ssh access only with a key, disable root login, and use an alternate port:

/etc/ssh/ssh_config.d/99-hardening.confg
# disable login methods that are not key-based
ChallengeResponseAuthentication no
PasswordAuthentication no
UsePAM no

# disable login as root
PermitRootLogin no

# use a non-standard port
Port <something different than 22>

On many Linux systems accessing as root via SSH is not possible, the Raspberry image is a notable exception.

I also enabled logging; it can be used to verify if there are multiple authentication attempts.

You need to apt install rsyslog, otherwise /var/log/auth.log will not even be present.

rsyslog is normally already available, but it might be if you have a minimal system.

root account

If you already use sudo or some alternate mechanism (like doas) for administrative tasks, then it is possible to "disable" the root account

# with admin rights
passwd -dl root

passwords

Use a stronger password. Ideally a passphrase; pick a book from your library and copy a random sentence, even better if not in English: "Hermione rimase in infermeria per diverse settimane." It does not have to be something memorable, you have a password manager for that, but it should be easy to type, dictate, and so on. Eventually add some numbers, exclamation marks, or other symbols.

passwd

Security updates

Contrary to most other machines, this one will be running even when I’m not using it actively, and I might not access it regularly. At least it is possible to install security updates automatically without too much effort.

apt install unattended-upgrades
# test with
unattended-upgrades --dry-run --debug
# verify if service is running
systemctl status unattended-upgrades

Verify open ports

The output of lsof -i -P or netstat -tulpen should enlist all currently open ports and applications listening on them.

Consider also installing a firewall manager (apt install ufw), close all ports except those that are needed, and use it for monitoring:

ufw disable
ufw default deny incoming
ufw allow 50022/tcp comment 'Open port for ssh'
ufw allow 51820/udp comment 'Open port for wireguard'
ufw enable
ufw status verbose

There is also the possibility to rate limit the connections, for example with.

ufw limit 50022/tcp comment 'port rate limit for ssh'

Setup WireGuard

Finally, the most important piece of this note.

WireGuard uses keys as authentication mechanism, the following snippets will help to configure two machines.

You can otherwise use the Wireguard Config Generator to create the necessary keys and configuration files.

execute on remote machine with admin rights
# add entries to local.conf if you want to use WireGuard as vpn for
# accessing internet
# if ouy only want to use as vpn for accessing local machines, then
# those changes are not required
echo 'net.ipv4.ip_forward=1'          >> /etc/sysctl.d/local.conf
echo 'net.ipv6.conf.all.forwarding=1' >> /etc/sysctl.d/local.conf
sysctl -p /etc/sysctl.d/local.conf

# install wireguard
apt install wireguard openresolv

# generate keys
wg genkey | tee /etc/wireguard/server_private.key | wg pubkey | tee /etc/wireguard/server_public.key

# create config file
# note that it needs the public key of the client, which is generated in the
# snippet after this one
printf '[Interface]
PrivateKey = %s
Address = 10.10.0.1/24
ListenPort = 51820
#DNS = <optional dns entry>
PostUp = iptables -A FORWARD -i wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE; ip6tables -A FORWARD -i wg0 -j ACCEPT; ip6tables -t nat -A POSTROUTING -o eth0 -j MASQUERADE # Add forwarding when VPN is started
PostDown = iptables -D FORWARD -i wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE; ip6tables -D FORWARD -i wg0 -j ACCEPT; ip6tables -t nat -D POSTROUTING -o eth0 -j MASQUERADE # Remove forwarding when VPN is shutdown

[Peer]
PublicKey = %s
AllowedIPs = 10.10.0.2/32
' "$(cat /etc/wireguard/server_private.key)" "$(cat /etc/wireguard/client_public.key)" > /etc/wireguard/wg0.conf

# start and verify wireguard service
systemctl enable wg-quick@wg0.service
systemctl status wg-quick@wg0
systemctl start  wg-quick@wg0.service
systemctl status wg-quick@wg0
execute on local machine with admin rights
apt install wireguard openresolv

# generate keys, I do not think they need to be there
wg genkey | tee /etc/wireguard/client_private.key | wg pubkey | tee /etc/wireguard/client_public.key

# ip address of RPI
server_address_and_port="12.34.56.78:51820"

printf '[Interface]
Address = 10.10.0.2/32
PrivateKey = %s
#DNS = <optional dns entry>

[Peer]
PublicKey = %s
# forward everything
AllowedIps = 0.0.0.0/0, ::/0
Endpoint = %s
PersistentKeepalive = 25
' "$(cat /etc/wireguard/client_private.key)" "$(cat /etc/wireguard/server_public.key)" "$server_address_and_port" > /etc/wireguard/wg0-client.conf

openresolv is not a dependency of wireguard, but required if you provide a DNS entry, otherwise you’ll get /usr/bin/wg-quick: line 31: resolvconf: command not found as error.

Once everything is setup up, you can connect with

wg-quick up wg0-client
wg show
#open webpage in browser that shows ip address, ping hangs
wg-quick down wg0-client

Access WireGuard from a different network

Unless your ISP garantees a static IP (most do not), you will need a Dynamic DNS

The idea is to have a fixed name in you configuration file on your computer:

[Interface]
Address = 10.10.0.2/32
PrivateKey = <client private key>

[Peer]
PublicKey = <server public key>
AllowedIps = 10.10.0.1/32
# dynamic dns
Endpoint = <ddns>:51820
PersistentKeepalive = 25

The raspberry should configure a website to redirect everything on it’s ip address.

In most cases, it should be not more complicated than to load a page with curl periodically (thus to create a cron job), if you are required to execute a binary blob or something complex, you might want to look for alternatives.

Do not forward everything to the VPN

Sometimes I do not want to forward everything, but just want to connect the Raspberry with ssh and verify if everything is OK or do some mantainance tasks.

In such cases, it is sufficient to forward the traffic only to a specific IP address:

/etc/wireguard/wg0-client-ssh.conf
[Interface]
Address = 10.10.0.2/32
PrivateKey = <client private key>

[Peer]
PublicKey = <server public key>
# 10.10.0.1 is the Interface address in wireguard of the raspberry
AllowedIps = 10.10.0.1/32
Endpoint = <ddns>:51820
PersistentKeepalive = 25

and enable/disable the VPN with wg-quick up wg0-client-ssh/wg-quick down wg0-client-ssh.

Enable logging

It is possible to enable logging in WireGuard through the following commands.

At runtime:

# with admin rights
modprobe wireguard
echo module wireguard +p > /sys/kernel/debug/dynamic_debug/control

At boot time:

# with admin rights
echo "wireguard" > /etc/modules-load.d/wireguard.conf
echo "options wireguard dyndbg=+p" > /etc/modprobe.d/wireguard.conf

Once configured, you can inspect the logs with

# with admin rights
journalctl --catalog --grep='wireguard*'
# or
dmesg --human --ctime --color=always | grep -i wireguard

Conclusion

That was it.

The only thing left to do is to place the Raspberry at the new location, connect it with an ethernet cable to the router, enable UDP port forwarding on the router, and you are ready to connect with wg-quick up wg0-client from a different country.

Currently, I’m mainly interested in accessing regional restricted resources, but this setup opens new use cases for reusing older devices.

Note 📝
I also tried using an Android phone instead of the raspberry, it would have made the device even more portable. Setting up wireguard is more or less the same process as on a normal computer. What does not work is forwarding the traffic, and thus use the phone to access resources that are geographically restricted. If you have administrative rights on the device, you might be able to set it up, but I’ve decided that the solution with the raspberry is good enough. Maybe another time I’ll try it again.

Do you want to share your opinion? Or is there an error, some parts that are not clear enough?

You can contact me anytime.