Setup WireGuard VPN on CentOS

WireGuard provides a modern VPN and is advertised as being much easier to install and configure than many other VPN services. Most of this simpler setup procedure comes from the fact that the cryptographic methods used are not as highly configurable as in other VPN solutions. WireGuard aims to use state of the art algorithms without overwhelming the VPN administrator with too many configuration options. With less configuration needed, setup becomes much simpler. In short, setting up WireGuard on CentOS only needs you to perform a handful of of tasks.

At the time of writing, WireGuard provides a Kernel module via DKMS (Dynamic Kernel Module Support). The Kernel module is compiled against the currently running kernel. You might notice issues with wiregard “not working” after installing it. This can be due to the dkms in some situations. This can be happen when the running kernel is not the same as the most recently installed kernel.

The above explains why the WireGuard installation instructions suggest to update and then reboot the system before starting the installation process.

To be able to install WireGuard on CentOS, the WireGuard repository needs to be setup. The repository is provided by the Fedora COPR, a service to build rpm packages and provides those as repositories. The repository can be created by setting the repository baseurl to the COPR repository location.

Create the repository file /etc/yum.repos.d/wireguard.repo and add the following content.

[WireGuard]
baseurl = https://copr-be.cloud.fedoraproject.org/results/jdoss/wireguard/epel-$releasever-$basearch/
enabled = 1
gpgcheck = 1
gpgkey = https://copr-be.cloud.fedoraproject.org/results/jdoss/wireguard/pubkey.gpg
name = WireGuard Repository - RHEL $releasever

To be able to install the required dependencies for WireGuard, the EPEL repository needs to be enabled as well. The CentOS base repository provides a package to setup the EPEL repository.

$ sudo yum install epel-release

With the repositories set up, the packages can be installed. The required packages are called wireguard-dkms and wireguard-tools. The additional qrencode package is not required to run WireGuard, but makes it easy to setup a client by creating a QR-code of the configuration that can then be scanned by the client.

$ sudo yum install wireguard-dkms wireguard-tools qrencode

WireGuard interface Configuration

To start up WireGuard, a special interface needs to be configured. In this example the interface will be called “wg0”. Before starting to configure the interface, a set of cryptographic keys need to be generated. The key pair generated in this step is the WireGuard server’s key pair.

$ /usr/bin/wg genkey >/etc/wireguard/server.key

The above wg(8) with the “genkey” command generates a private key. To store the private key in a file, the command output is redirected into the file “/etc/wireguard/server.key”. To generate the public key from this private key, wg(8) provides the “pubkey” command.

$ cat /etc/wireguard/server.key | wg pubkey

In the above command the public key is shown as output. This public key will be required later to configure the client. Create a config file for WireGuard at “/etc/wireguard/wg0.conf”. This configuration file will contain the configuration of the interface as well as configuration sections for each client. The interface configuration section will look like this.

[Interface]
Address = 10.20.30.1/24
Address = AAAA:BBBB:CCCC::1/64
ListenPort = 51820
PrivateKey = WOyiiKlEl4mMNQOxjPmWNRuYQYxb2rLPdXFMmeYkhXc=

“[Interface]” marks the start of the section configuring the interface.
“ListenPort” The listen port used by WireGuard for incoming connections. This port needs to be opened in the firewall as well.
“PrivateKey” The private key from the file /etc/wireguard/server.key as generated above.
“Address” is used to set the IP address of the WireGuard interface itself. It can be used multiple times to set multiple IP addresses. The example shows how two Address lines can be used to set an IPv4 and an IPv6 address.

The interface is now configured and the WireGuard interface can be started up. At this point there is no peer (client) configured. Still, the interface can be started to bring up the interface with its IP address(es).

$ systemctl start wg-quick@wg0

The service name “wg-quick@wg0” does not exist before the service is called for the first time. The specific systemd unit-file is a template unit “wg-quick” requiring an instance, the interface name “wg0”. When started, the service will configure and bring up the named interface with the configured IP address(es).

Using the wg-quick(8) service is the simplest way to start WireGuard. wg-quick(8) adds a few more settings to the WireGuard interface configuration for example the interface IP address(es). This additional settings allow wg-quick(8) to bring up the interface as well as start WireGuard. A look into the log output of the wg-quick service reveals the tasks performed.

$ journalctl -u wg-quick@wg0 

WireGuard peer configuration

The WireGuard client is called a peer in the configuration. As before with the Interface configuration, the peer needs a key-pair as well. The key pair can be generated on the client itself. In this example, the private key for the peer will also be generated on the WireGuard server.

$ /usr/bin/wg genkey >/etc/wireguard/peer01.key
$ cat /etc/wireguard/peer01.key | wg pubkey

The WireGuard interface configuration at /etc/wireguard/wg0.conf needs to be updated to add a new peer. For each peer added to the configuration, a “[Peer]” section is added as described below.

[Peer]
AllowedIPs = 10.20.30.101/32
AllowedIPs = AAAA:BBBB:CCCC::101/128
PublicKey = zcozQs98MD+71RDeIxLyyJkUKnxfdQRxo7twZv63lhM=
PresharedKey = +zKPQpwmIgDbISCpohOz8HXMPE/rPiNMCYUQBsFnAR0=

“[Peer]” start off the section for a peer. For each peer, one section will be added to the interface configuration file. (in this context each client is a peer)
“AllowedIPs” IP address and CIDR mask from which traffic is expecterd to come for this peer. In this example, this is the IP of the client. Multiple addresses can be provided by multiple lines or as comma-separated list.
“PublicKey” the public key of the peer generated from the private key of the peer (in this example from peer01.key).
“PresharedKey” an optional setting to add an extra layer of symmetric encryption. A pre-shared key can be generated for each peer separately using wg genpsk.

With the interface configuration updated, the WireGuard interface can be restarted to load the added peer.

$ systemctl restart wg-quick@wg0

A quick check with wg shows the interface and the peers configured.

$ wg
interface: wg0
  public key: W7jdQsit1c+MbNrWdrf35liGmLuIZIK+SPdul3TnREM=
  private key: (hidden)
  listening port: 51820

peer: zcozQs98MD+71RDeIxLyyJkUKnxfdQRxo7twZv63lhM=
  preshared key: (hidden)
  allowed ips: 10.20.30.101/32, aaaa:bbbb:cccc::101/128

At this point it would be necessary to open port that WireGuard is listening on in the firewall. The listening port can be changed in the interface configuration and then also needs to be used in the client configuration described below.

Configure the WireGuard client

With the peer configured on the WireGuard interface, the client can be configured. The syntax of the client configuration is very similar to the interface configuration on the server. Depending on the client, the configuration details can be entered into the WireGuard client (or imported) or scanned via QR-code. In this example the client config file will be called “peer01.conf”. This config file can be imported to the client or used to generate the QR-code,

[Interface]
Address = 10.20.30.101/32
Address = AAAA:BBBB:CCCC::101/128
PrivateKey = IJADTBB+Eg/iiUUwZ+TbJKljRXMByZv0stKxWY7AMVg=
DNS = 10.20.30.1
DNS = AAAA:BBBB:CCCC::1

[Peer]
PublicKey = W7jdQsit1c+MbNrWdrf35liGmLuIZIK+SPdul3TnREM=
PresharedKey = +zKPQpwmIgDbISCpohOz8HXMPE/rPiNMCYUQBsFnAR0=
Endpoint = 10.0.0.123:51820
AllowedIPs = 10.20.30.0/24
AllowedIPs = AAAA:BBBB:CCCC::/64

“[Interface]” marks the start of the section configuring the Interface.
“Address” is used to set the IP address of the WireGuard interface of the client. It can be used multiple times to set multiple IP addresses. The example shows how two Address lines can be used to set an IPv4 and an IPv6 address.
“PrivateKey” The private key of this peer from the file /etc/wireguard/peer01.key as generated before.
“DNS” sets the DNS servers for this interface. A comma separated list or multiple entries can be used.

“[Peer]” start of the section for a peer. In the client configuration here, the peer is the WireGuard server.
“PublicKey” the public key of the server, generated from the private key of the peer (in this example from server.key).
“PresharedKey” the optional setting to add an extra layer of symmetric encryption. This PresharedKey must be the same as the one used in the interface configuration.
“Endpoint” is the WireGuard server address and port.
“AllowedIPs” IP address and CIDR mask for the traffic to be routed through the VPN. With 0.0.0.0/0 and ::/0 all traffic is routed through the vpn connection. This setting controls the routes set when the interface is brought up.

With the configuration written to a file, a qr-code can be generated from it to be scanned from a WireGuard mobile client. In the command line, qrencode(1) can be used to generate a qr-code from the config file. Depending on the supported character set, use the “–type” option to specify the output format.

$ cat peer01.conf | qrencode --type utf8

Test the connection

With the mobile client its just a matter of scanning the generated qr-code. With the desktop client, the easiest way is to import the configuration file into the client.

On CentOS, wg-quick(8) can be used to start the connection as a client. For wg-quick to find the configuration file, store it in “/etc/wireguard/”. As with the server side configuration, the filename represent the interface name. In this example, the file is called /etc/wireguard/peer01.conf which will configure an Interface “peer01”.

$ wg-quick up peer01
[#] ip link add peer01 type wireguard
[#] wg setconf peer01 /dev/fd/63
[#] ip -4 address add 10.20.30.101/32 dev peer01
[#] ip -6 address add AAAA:BBBB:CCCC::101/128 dev peer01
[#] ip link set mtu 1420 up dev peer01
[#] ip -6 route add aaaa:bbbb:cccc::/64 dev peer01
[#] ip -4 route add 10.20.30.0/24 dev peer01

With the configuration file in “/etc/wireguard”, the up command just needs the interface name “peer01”. The output of the command shows the performed tasks to get the WireGuard interface up. The “address” lines define the IP address of the WireGuard interface and the AllowedIPs are used to set routes through the WireGuard interface.

$ wg
interface: peer01
  public key: W7jdQsit1c+MbNrWdrf35liGmLuIZIK+SPdul3TnREM=
  private key: (hidden)
  listening port: 51820

peer: zcozQs98MD+71RDeIxLyyJkUKnxfdQRxo7twZv63lhM=
  preshared key: (hidden)
  allowed ips: 10.20.30.101/32, aaaa:bbbb:cccc::101/128

The wg(8) command can be used to show the WireGuard connection. If no “PersistentKeepalive” is set, the wg command will not show any details after the interface is brought up. After the first data is sent through the WireGuard interface, the wg command will show connection details and statistics.

$ ping -c 1 10.20.30.1

To trigger the first data sent through the WireGuard interface ping(8) can be used. When the “PersistentKeepalive” option is set, bringing up the interface seems to trigger the first keepalive package to be sent and the connection statistic is immediately visible.

$ wg
interface: peer01
  public key: zcozQs98MD+71RDeIxLyyJkUKnxfdQRxo7twZv63lhM=
  private key: (hidden)
  listening port: 40468

peer: W7jdQsit1c+MbNrWdrf35liGmLuIZIK+SPdul3TnREM=
  preshared key: (hidden)
  endpoint: 10.0.0.123:51820
  allowed ips: 10.20.30.0/24, aaaa:bbbb:cccc::/64
  latest handshake: 2 seconds ago
  transfer: 220 B received, 276 B sent

After the first data was sent through the WireGuard interface, the output of wg(8) shows the last handshake as well as transfer counter.

To stop the WireGuard connection, the down command can be executed.

$ wg-quick down peer01

As with the WireGuard configuration on the server, the client side can also be started with systemd using the “wg-quick” unit.

$ systemctl start wg-quick@peer01

This can be used to, for example, start the WireGuard connection when the client boots up.

Troubleshooting Tip

If the the WireGuard server is used to access the internet or the network behind the WireGuard server, IP forwarding must be enabled as well on the WireGuard server. For more details on routing traffic checkout Access the internet from server without direct connection.

For debugging connection issues, tcpdump(8) seemed to be the best option. With tcpdump on, the interface used to connect to WireGuard will show if the client sends any packets to the WireGuard server.

$ tcpdump -vv -i eth0 udp port 51820

With nc(1), UDP packets can be send to the WireGuard server. WireGuard will not respond to this, but it can help to verify the UDP packets arrive as intended over the network.

$ echo -n "something" | nc -v -u 10.0.0.123 51820

Tcpdump can also be used to check the incoming traffic on the WireGuard interface. This way the traffic that is sent through the VPN can be shown. Without defining a filter for tcpdump(8), the amount of packages can be a bit much depending on what might need troubleshooting.

$ tcpdump -vv -i wg0 

Read more of my posts on my blog at https://blog.tinned-software.net/.

This entry was posted in Encryption, Linux Administration and tagged , . Bookmark the permalink.