Wireguard VPN Server on FreeBSD
Wireguard is a fast, modern, and simple VPN system that can be used to create many different setups. I use it for various purposes on my servers, for example to connect my servers to each other, but also to access my internal services (including Jellyfin Media Server) on the go via clients such as my cell phone. Here is a quick guide on how I set up my VPN server and add new clients.
Installation
FreeBSD has a kernel module for WireGuard, which is already available by default. Therefore, we only need to install the necessary tools. In addition, I would like to transfer my configurations to the clients via QR code.
pkg install wireguard-tools libqrencode
Configuration
The configuration is very quick to complete. First, you need keys for the server. The commands should be executed as root in /usr/local/etc/wireguard:
umask 077
wg genkey > server.key
wg genpsk > server.psk
wg pubkey < server.key > server.pub
Now create an interface configuration. For example, the file wg0.conf:
[Interface]
Address = 172.16.3.1/24
ListenPort = 12345
PrivateKey = <KEY in server.key>
# Clients:
I haven’t configured a client here. Instead, I use a small script to flexibly add new clients. To do this, I created the directory wg0_clients, where I create the client configurations. I then have two shell scripts in this directory. I use one to create the configuration for my client, and the second outputs the corresponding configuration as a QR code on my terminal so that I can add the VPN with a cell phone using a QR code scanner. Here is the first script for creating the configuration
#!/usr/local/bin/bash
# Configurable variables
SERVER_PUBLIC_KEY="SERVER PUBLIC KEY (server.pub)"
SERVER_IP="vpn.example.com"
SERVER_PORT="12345"
PRIVATE_DIR="/usr/local/etc/wireguard/wg0_clients"
SERVER_CONF="/usr/local/etc/wireguard/wg0.conf"
# get client name
if [ $# -ne 2 ]; then
echo "A client name and IP must be specified"
echo "./create_conf.sh CLIENTNAME IP"
exit 1
fi
CLIENT_NAME=$1
CLIENT_IP=$2
CLIENT_PRIVATE_KEY=$(wg genkey)
CLIENT_PUBLIC_KEY=$(echo $CLIENT_PRIVATE_KEY | wg pubkey)
CLIENT_PSK=$(wg genpsk)
# create client_conf
cat > $PRIVATE_DIR/$CLIENT_NAME.conf << EOF
[Interface]
PrivateKey = $CLIENT_PRIVATE_KEY
Address = $CLIENT_IP/32
[Peer]
PublicKey = $SERVER_PUBLIC_KEY
PreSharedKey = $CLIENT_PSK
Endpoint = $SERVER_IP:$SERVER_PORT
AllowedIPs = 0.0.0.0/0
PersistentKeepalive = 60
EOF
# write Server Conf
echo "#Client $CLIENT_NAME" >> $SERVER_CONF
echo "[Peer]" >> $SERVER_CONF
echo "PublicKey = $CLIENT_PUBLIC_KEY" >> $SERVER_CONF
echo "PreSharedKey = $CLIENT_PSK" >> $SERVER_CONF
echo "AllowedIPs = $CLIENT_IP" >> $SERVER_CONF
This script is given a name for the client. It generates the necessary keys and writes a client config. It also adds to the server’s configuration file so that it now has a client. To start the Wireguard server, Wireguard must be enabled and the interfaces must be written to rc.conf. The server should also serve as a gateway, so we must activate this as well. To do this, add the following lines to /etc/rc.conf
wireguard_enable="YES"
wireguard_interfaces="wg0"
gateway_enable="YES"
You can then start the service with:
service wireguard start
The following command displays the status of the clients:
wg show
When adding new clients with the above script, you must restart the service once:
service wireguard restart
To output the client configuration as a QR code, I use the following script, which mainly helps me remember the necessary command:
#!/usr/local/bin/bash
if [ $# -ne 1 ];then
echo "./print_qr.sh CLIENT"
exit 1
fi
CLIENT=$1
qrencode -t ansiutf8 < $CLIENT.conf
Firewall
If you have a firewall on or in front of your server, you must of course enable the corresponding port. Wireguard uses UDP for data traffic. You also need NAT so that clients can access public networks. Here is an example for the pf firewall under FreeBSD:
nat on $external_if inet from $vpn_net to any -> $server_pub_ip
pass in on $external_if proto udp from any to $server_pub_ip port 12345
The macros $vpn_net, $external_if, and $server_pub_ip must of course be defined beforehand. However, since everyone has different requirements for their server, I will not go into further detail here.