WireGuard is according to Wikipedia “a communication protocol and free and open-source software that implements encrypted virtual private networks”, and per the Wiregard page “extremely simple yet fast and modern”. I don’t have experience with any other VPNs so don’t know what they are like, but if done the right way I think Wireguard is quite easy to set up and get working.
It is however lacking a little when it comes to documentation. The commandline animations on their page I find a little annoying, especially the one that can’t be paused. Fortunately there are plenty of pages with details on how to easily set it up.
So why this page then? I want to have a go-to place for the config file as that is my preferred way of setting up wireguard, instead of having to use a bunch of somewhat obscure commands I never remember in order to configure the interface manually.
I will detail setups for Ubuntu Linux, FreeBSD and OpenBSD. Linux and FreeBSD are quite similar while the setup for OpenBSD is only slightly different.
Vultr has a good guide for setting up Wireguard on FreeBSD 14 in case you want other words on how to set up and configure it. The configuration file is pretty much the same independent of the platform, so it’s just the commands to start and stop the service that can be different.
To install, please refer to the wireguard.com/install page as they are most likely more up to date than what this page will be when it comes to installation for your particular platform.
Wireguard is already in the FreeBSD base system, and you have the wg
command
available if you want to set it up that way. By installing the package you
will get the wg-quick
tool as well and the startup script using it will
be added. Wg-quick
is available on all platforms I’ve tested, but for
OpenBSD but I found it easier to just use wg
and hostname configuration.
The advantage to using wg-quick
is that it will set up the virtual interface
for you, but configuration files for wg-quick and wg aren’t 100% compatible.
On OpenBSD then it is necessary to add a /etc/hostname.wg0
file to configure
the interface, with the benefit that it will also function as the startup
script.
I find it easiest to execute the rest of the commands as root as the
wireguard directory has, and should have restrictive permissions. If you’re
following this for FreeBSD remember to be in /usr/local/etc
.
# cd /etc/wireguard
# umask 077
The umask
command makes sure the files we create will also have restrictive
permissions, meaning read and write of them is only available to the current
user, in other words root.
One basis of Wireguard is that it uses key-value pairs. You will be creating a private and a public key, and the ones we connect with will do the same. The private key will be kept secret, while the public key will be exchanged. We will also create a pre-shared key, and if using that it should be done for each party you connect with. The private and public keys are generated only once.
To create the keys, use the wg
utility:
# wg genkey >private.key
# wg pubkey < private.key >public.key
# wg genpsk >pre-shared.key
The wg pubkey < private.key
means that the public key is derived from the
private key, as the private key is input to the command. Another way to write
it would be
# cat private.key | wg pubkey >public.key
Or if you know your commandline-foo everything can be done in one line:
# wg genkey | tee private.key | wg pubkey > public.key
The pre-shared key is not strictly necessary, but it adds a symmetric encryption key to the mix making this a little more future-proof should there ever be quantum-computers powerful enough to crack the asymetric encryption being used. It can probably be assumed that state-actors are storing most if not all internet traffic, with the purpose of decrypting everything when and if said computers become available so I think it’s just good practice.
Now why doesn’t everyone just use symmetric encryption then? Because transporting that symmetric key safely is a non-trivial problem.
In any case, let’s get started setting up the configuration on our side and add the first peer, and then do the same on the peer to establish communication between the two.
I’ve created the following keys to be used in this example, which you should absolutely not use! I am not using them myself, I just wanted to show actual values so it’s easy to see what everything should look like.
Peer A (us):
PrivateKey: qOncWvsVv7hTlgD2d++5Sx1ULjTa7zeKHKbuTl1J6GI=
PublicKey: q9jepA++1o/7bi2wbQaBOG2KUE8/2cz0i29aiv0MXlA=
PreSharedKey: kgC1FhcRNbKj3w5ew33mF5WvIKwMLGY7f5iuy3PBclY=
Peer B (them):
PrivateKey: CCfMo0ytoDPCPqxNPWAVAUka+no+c2NwivtBoNd21EM=
PublicKey: lmWvq5Gv8ZyV+9ruZF9APjffGEu0uEDJ5kANdeX6UWY=
Get the private key you created earlier and create the following file in
/etc/wireguard
: wg0.conf
[Interface]
PrivateKey = qOncWvsVv7hTlgD2d++5Sx1ULjTa7zeKHKbuTl1J6GI=
Address = 10.0.0.1/32
ListenPort = 51820
This will set up a virtual interface called wg0, it specifies that when we send traffic through the tunnel we will be using ip address 10.0.0.1 and it specifies that we listen on UDP port 51820 for anyone that wants to connect to us. The listen port can be changed to anything you want, just remember to open the firewall accordingly as well as letting the clients know about it.
A small note here about the difference between Linux/FreeBSD and OpenBSD is
that the wg-quick
tool being used expects the netmask, the /32
in this
example in the address. For OpenBSD or when just using wg
, the netmask is
not understood and will throw an error, so it should be removed.
Now this configuration in itself isn’t terribly useful as it won’t actually let anyone connect to us. To allow connections, we have to specify [Peer] segments with the peer’s configuration. This should contain their public key and a pre-shared key if we are using that. As mentioned previously you can and probably should have different pre-shared keys for each peer.
We also need to give our public key to them as they have to add a [Peer] section on their side for us. In addition we need to know their public ip if we are to esablish connections to them and/or they need to know our public ip if they are to connect to us.
Extend wg0.conf
so it now looks like this:
[Interface]
PrivateKey = qOncWvsVv7hTlgD2d++5Sx1ULjTa7zeKHKbuTl1J6GI=
Address = 10.0.0.1/32
ListenPort = 51820
[Peer]
PublicKey = lmWvq5Gv8ZyV+9ruZF9APjffGEu0uEDJ5kANdeX6UWY=
PreSharedKey = kgC1FhcRNbKj3w5ew33mF5WvIKwMLGY7f5iuy3PBclY=
AllowedIPs = 10.0.0.2/32
EndPoint: 2.2.2.2:51820
This specifies that we only allow traffic from ip 10.0.0.2 through the tunnel for this peer, and that we can initiate a connection to it on public ip 2.2.2.2 on port 51280. As soon as we’ve connected to the peer they will know our public ip and can connect back if needed, but only as long as there has been a connection recently.
On the peer, their configuration should look like this:
[Interface]
PrivateKey = CCfMo0ytoDPCPqxNPWAVAUka+no+c2NwivtBoNd21EM=
Address = 10.0.0.2/32
ListenPort = 51820
[Peer]
PublicKey = q9jepA++1o/7bi2wbQaBOG2KUE8/2cz0i29aiv0MXlA=
PreSharedKey = kgC1FhcRNbKj3w5ew33mF5WvIKwMLGY7f5iuy3PBclY=
AllowedIPs = 10.0.0.1/32
EndPoint: 1.1.1.1:51820
To start everything on Linux/FreeBSD, execute this command as root:
# wg-quick up wg0
You should now be able to see a new wg0
interface with command ip a
, and
you should see the configured peers with command wg
. On peer A, us:
# wg
interface: wg0
public key: q9jepA++1o/7bi2wbQaBOG2KUE8/2cz0i29aiv0MXlA=
private key: (hidden)
listening port: 51820
peer: lmWvq5Gv8ZyV+9ruZF9APjffGEu0uEDJ5kANdeX6UWY=
preshared key: (hidden)
endpoint: 2.2.2.2:51820
allowed ips: 10.0.0.2/32
latest handshake: 2 hours, 7 minutes, 37 seconds ago
transfer: 2.10 MiB received, 2.34 MiB sent
On peer B, them:
# wg
interface: wg0
public key: lmWvq5Gv8ZyV+9ruZF9APjffGEu0uEDJ5kANdeX6UWY=
private key: (hidden)
listening port: 51820
peer: q9jepA++1o/7bi2wbQaBOG2KUE8/2cz0i29aiv0MXlA=
preshared key: (hidden)
endpoint: 1.1.1.1:51820
allowed ips: 10.0.0.1/32
latest handshake: 2 hours, 4 minutes, 45 seconds ago
transfer: 2.34 MiB received, 2.10 MiB sent
Then to bring it down again:
# wg-quick down wg0
Notice that you might have to allow traffic on the wg0
interface through the
firewall of your system. Then you should be able to ping the other side using
the private address, either 10.0.0.1
or 10.0.0.2
and see “latest
handshake” and “traffic” update with the wg
command.
For OpenBSD, to complete the setup, create the file /etc/hostname.wg0
and
add ip configration to it:
inet 10.0.0.1 255.255.255.0 NONE
up
!/usr/local/bin/wg setconf wg0 /etc/wireguard/wg0.conf
Then start wireguard with sh /etc/netstart wg0
. This file will also ensure
that wireguard starts at boot.
The setup so far only allows reaching services on the peers, as defined by
the firewall rules. If traffic should be routed through the peer and onto the
Internet, the operating system needs to allow forwarding packets. This is
configured with sysctl
:
Linux:
# sysctl net.ipv4.ip_forward=1
# sysctl net.ipv6.conf.all.forwarding=1
To make this permanent, edit /etc/sysctl.conf
and uncomment the lines that
looks like the ones above, but without the sysctl
command first. If there
are no such lines, simply add them:
net.ipv4.ip_forward=1
net.ipv6.conf.all.forwarding=1
FreeBSD and OpenBSD are similar:
# sysctl net.inet.ip.forwarding=1
# sysctl net.inet6.ip6.forwarding=1
Add the lines to /etc/sysctl.conf
to make this permanent:
net.inet.ip.forwarding=1
net.inet6.ip6.forwarding=1
It is important to note that there should be no spaces here. In other words,
net.inet.ip.forwarding = 1
won’t work.
You might also have to configure the firewall to allow forwarding packets and also do NAT for ipv4. That is out of scope for this document, but I might get back to it later.
To start wireguard with systemd and enable start at boot on Ubuntu Linux:
$ sudo systemctl enable wg-quick@wg0
$ sudo systemctl start wg-quick@wg0
On FreeBSD:
$ sudo sysrc wireguard_interfaces="wg0"
$ sudo sysrc wireguard_enable="YES"
$ sudo service wireguard start
To stop the service use the same scripts with stop
instead of start
.
On OpenBSD use the ifconfig
command:
# ifconfig wg0 destroy
To add more peers to the network, simply add more [Peer] blocks like so:
[Interface]
PrivateKey = qOncWvsVv7hTlgD2d++5Sx1ULjTa7zeKHKbuTl1J6GI=
Address = 10.0.0.1/32
ListenPort = 51820
[Peer]
PublicKey = lmWvq5Gv8ZyV+9ruZF9APjffGEu0uEDJ5kANdeX6UWY=
PreSharedKey = kgC1FhcRNbKj3w5ew33mF5WvIKwMLGY7f5iuy3PBclY=
AllowedIPs = 10.0.0.2/32
EndPoint: 2.2.2.2:51820
[Peer]
PublicKey = 6D8qMzetUQH7dOUZeuPXYj/wdXiA4nQswaQMCXA3GXU=
PreSharedKey = KEYgEdBieJG4UwTQpz3xrElTHvoWSH/Xi2ox7XnfJxg=
AllowedIPs = 10.0.0.3/32
[Peer]
PublicKey = fzECqbQdzi1ce6DYjJiNISct9NW5n5nn7WyIrYqjvwM=
PreSharedKey = Jt2uGv80J+z5jCa0W7Js+s3pkzYM+gmTNiGm8nkB8r8=
AllowedIPs = 10.0.0.4/32
EndPoint: 3.3.3.3:51820
Then use the scripts to restart/reload the configuration.
On OpenBSD the configuration can be added from disk to the running service
with wg
:
# wg syncconf wg0 /etc/wireguard/wg0.conf
That should be it for now. Hope it was helpful!