An introduction to WireGuard VPN
1. Why WireGuard?
When it comes to encrypting traffic between systems, there are a wide variety of different Virtual Private Network (VPN) software available, some of which have been used since the 1990s. Each of them focus on different encryption algorithms and flow control strategies, alongside mechanisms for providing for secure authentication and negotiating encryption keys. Unfortunately, this complexity often translates to more problems, slower traffic, as well fewer use cases and supported operating systems.
WireGuard uses high-performance strong cryptography, such as ChaCha20 (for symmetric data encryption) and Curve25519 (for asymmetric key negotiation), alongside a framework similar to SSH and Git. Moreover, it provides VPN functionality only when traffic is sent, doesn’t include complex authentication mechanisms, and is available for all desktop and mobile operating systems. In short, WireGuard is a cross-platform VPN that minimizes bandwidth and maximizes data transfer speed while boasting top-notch security and a lower attack surface.
2. A basic VPN configuration
Like its name suggests, a VPN is a virtual network that overlays your ordinary network. When data is sent on this virtual network, it is automatically encrypted to ensure that the data remains private. We often say that this data is tunnelled through the VPN.
WireGuard does not have a separate client and server component - each system that participates in a WireGuard VPN is considered equal and called a peer in WireGuard documentation. However, it’s easier to visualize communication when we think in terms of clients and servers, so we’ll call one system a client and the other a server. And while WireGuard works equally well with IPv4 or IPv6 networks, we’ll stick to IPv4 for readability.
The following diagram shows a client (IP address 192.168.1.107) and server (IP address 192.168.1.106) connected to the same IPv4 LAN (192.168.1.0/24). After creating a VPN, each system will have a second IP address on the VPN (e.g. 172.16.0.1 for the client and 172.16.0.99 for the server). In the following sections, we’ll implement this basic VPN configuration using WireGuard, and then discuss the configuration for other use cases.
3. Installing WireGuard
Visit https://www.wireguard.com/install/ to see how to install WireGuard on your operating system. You can usually install WireGuard from your Linux or BSD UNIX repository. For Windows, Android, macOS and iOS there is an app you can get, but you should avoid the macOS app and instead use the Homebrew package manager method (the macOS app is problematic at the time of this writing). Since the client and server I use run Fedora Linux, I ran the dnf install wireguard-tools
command as root (or via sudo
) to install WireGuard on them.
4. Configuring WireGuard
The first step for configuring WireGuard is to generate an asymmetric public/private key pair on each system that will participate in WireGuard. To generate and save the private key to the file privatekey
, as well as generate and save the associated public key to the file publickey
and view the results, you can run the following commands on both your server and client:
[root@server ~]# wg genkey | tee privatekey | wg pubkey > publickey
[root@server ~]# cat privatekey
+FqYdSx+rIg2gwwyd3hCfap/1Vz3z2UuRZCPKKwMaXw=
[root@server ~]# cat publickey
cjmyZf4c+6U3pD2QT+6Bxkjj9qzU8EePjc8dSeuXvWs=
[root@client ~]# wg genkey | tee privatekey | wg pubkey > publickey
[root@client ~]# cat privatekey
0EQpGsSfGwVRdxcCywG2ymnLG7mjmv+rB02UodcH10k=
[root@client ~]# cat publickey
8pfWwwPK8R+Qe/fuN5FZ0P2ddngWd8s79sOQw5Q7SXE=
Next, you must construct a WireGuard interface on each system that has a virtual private IP address and port number of your choice. The first WireGuard interface is called wg0, and should use the private key you generated and saved to the privatekey
file earlier (you don’t need to specify the public key as WireGuard will automatically generate it from the private key).
To configure a new wg0 interface on the server that listens on port 55234 using the virtual private IP 172.16.0.99 and view the results, you can run the following commands:
[root@server ~]# ip link add wg0 type wireguard
[root@server ~]# ip addr add 172.16.0.99/24 dev wg0
[root@server ~]# wg set wg0 private-key ./privatekey listen-port 55234
[root@server ~]# ip link set wg0 up
[root@server ~]# wg
interface: wg0
public key: cjmyZf4c+6U3pD2QT+6Bxkjj9qzU8EePjc8dSeuXvWs=
private key: (hidden)
listening port: 55234
To configure a new wg0 interface on the client that listens on port 55123 using the virtual private IP 172.16.0.1 and view the results, you can run the following commands:
[root@client ~]# ip link add wg0 type wireguard
[root@client ~]# ip addr add 172.16.0.1/24 dev wg0
[root@client ~]# wg set wg0 private-key ./privatekey listen-port 55123
[root@client ~]# ip link set wg0 up
[root@client ~]# wg
interface: wg0
public key: 8pfWwwPK8R+Qe/fuN5FZ0P2ddngWd8s79sOQw5Q7SXE=
private key: (hidden)
listening port: 55123
Now that you have a wg0 interface on both systems, you must add the client’s public key, IP and port number on the server, as well as add the server’s public key, IP and port number on the client in order to allow the systems to identify and communicate with each other. You must also specify the IP addresses or networks you will allow over WireGuard. You can do all of this with one command on each system:
[root@server ~]# wg set wg0 peer 8pfWwwPK8R+Qe/fuN5FZ0P2ddngWd8s79sOQw5Q7SXE= allowed-ips 172.16.0.0/16 endpoint 192.168.1.107:55123
[root@client ~]# wg set wg0 peer cjmyZf4c+6U3pD2QT+6Bxkjj9qzU8EePjc8dSeuXvWs= allowed-ips 172.16.0.0/16 endpoint 192.168.1.106:55234
The allowed-ips 172.16.0.0/16
argument in these commands is important to note. When WireGuard is sending information to the other system, 172.16.0.0/16 is treated like a target route. In other words, if your system sends data to the 172.16.0.0/16 network, it triggers WireGuard to start the VPN. However, when receiving information from the other system, 172.16.0.0/16 is treated like an access control list. In other words, your system won’t accept VPN traffic unless it originates from the 172.16.0.0/16 network.
That’s it, you now have WireGuard ready to go! Let’s ping
the virtual private IP of the server (172.16.0.99) from the client and view the output of wg
on both systems:
[root@client ~]# ping 172.16.0.99
<output omitted>
[root@client ~]# wg
interface: wg0
public key: 8pfWwwPK8R+Qe/fuN5FZ0P2ddngWd8s79sOQw5Q7SXE=
private key: (hidden)
listening port: 55123
peer: cjmyZf4c+6U3pD2QT+6Bxkjj9qzU8EePjc8dSeuXvWs=
endpoint: 192.168.1.106:55234
allowed ips: 172.16.0.0/16
latest handshake: 18 seconds ago
transfer: 8.21 KiB received, 15.10 KiB sent
[root@server ~]# wg
interface: wg0
public key: cjmyZf4c+6U3pD2QT+6Bxkjj9qzU8EePjc8dSeuXvWs=
private key: (hidden)
listening port: 55192
peer: 8pfWwwPK8R+Qe/fuN5FZ0P2ddngWd8s79sOQw5Q7SXE=
endpoint: 192.168.1.107:51423
allowed ips: 172.16.0.0/16
latest handshake: 27 seconds ago
transfer: 7.01 KiB received, 11.39 KiB sent
5. Making WireGuard configuration persistent
When you reboot, your wg0 interface and WireGuard configuration is gone. All that is left are your privatekey
and publickey
files. Thus, you would need to repeat the same commands on both systems again to set up the same WireGuard VPN.
For organizations that use Infrastructure as Code (IaC), the necessary commands and keys could be placed in their automation software (e.g. Ansible) or Continuous Deployment (CD) orchestration software (e.g. Kubernetes). Alternatively, you could place these commands within a shell script and configure your system to execute it at boot time. Since the private and public keys are already generated, you could create the following BASH shell script on the client, for example:
[root@client ~]# cat wireguard-client.sh
#!/bin/bash
ip link add wg0 type wireguard
ip addr add 172.16.0.1/16 dev wg0
wg set wg0 private-key ./privatekey listen-port 55123
ip link set wg0 up
wg set wg0 peer cjmyZf4c+6U3pD2QT+6Bxkjj9qzU8EePjc8dSeuXvWs= allowed-ips 172.16.0.0/16 endpoint 192.168.1.106:55234
However, a better method for making your WireGuard configuration persistent is to generate a WireGuard configuration file from wg0 and save it to the /etc/wireguard/wg0.conf
file. You’ll also need to copy your privatekey
and publickey
files to the same directory and ensure that only root has read and write permission to the contents of the /etc/wireguard/
directory.
To do this on the client, and view the results, you can run the following commands:
[root@client ~]# wg showconf wg0 > /etc/wireguard/wg0.conf
[root@client ~]# cp *key /etc/wireguard/
[root@client ~]# chmod 600 /etc/wireguard/*
[root@client ~]# cat /etc/wireguard/wg0.conf
[Interface]
Address = 172.16.0.1/16
ListenPort = 55123
PrivateKey = 0EQpGsSfGwVRdxcCywG2ymnLG7mjmv+rB02UodcH10k=
[Peer]
PublicKey = cjmyZf4c+6U3pD2QT+6Bxkjj9qzU8EePjc8dSeuXvWs=
AllowedIPs = 172.16.0.0/16
Endpoint = 192.168.1.106:55234
After creating /etc/wireguard/wg0.conf
, you can use the wg-quick up wg0
and wg-quick down wg0
commands to activate and deactivate wg0, or set your system to automatically activate wg0 at boot time using systemctl enable wg-quick@wg0.service
.
You can connect multiple clients to the same server. In this case, the server will need to know the PublicKey, IP and port for each client, so you will have multiple [Peer]
sections in the /etc/wireguard/wg0.conf
file on the server, one for each client. After adding a new [Peer]
section to this file, you must run the wg-quick down wg0 ; wg-quick up wg0
command on the server to activate the new configuration.
6. Using WireGuard for remote access to a network
While having a VPN between a client and a server allows for encrypted communication between them, the most common use case for a VPN is to encrypt communication between a client and a network (typically a corporate network). In this case, the server functions as a router on this target network, and will route packets from the client to the target network after decrypting them. To ensure that all client traffic is forwarded to the server across the VPN, the client uses the VPN as their default gateway route. Moreover, if the resource the client requests is not on the corporate network, then the server will use its own default gateway route to sent the traffic out the corporate Internet connection.
This use case is called remote access (or site access), and allows a client to securely access the resources on a corporate network from across the Internet as if their system was physically located on it.
The configuration of a remote access VPN depends on the structure of your target network. A sample network is shown below. Both home and corporate LANs connect to the Internet via their Internet Service Provider (ISP). On the corporate side, the ISP often provides a demarcation point (demarc) router that provides a public IP address to a Next Generation Firewall (NGFW). In addition to providing security functionality, the NGFW is often configured as a Network Address Translation (NAT) router to allow corporate LANs to access the Internet using private IP address ranges, such as the 10.0.x.0/24 networks shown below for the demilitarized zone (DMZ), as well as the department LANs that connect to the DMZ using a regular router. The client shown below also has a private IP address (192.168.1.107) because it’s usually on a home network behind a NAT router as well.
There are two main configuration methods for remote access, depending on whether the WireGuard server is located behind the NGFW, or directly connected to the demarc.
Method 1: Remote Access using a WireGuard server behind a NGFW
In the configuration shown below, the WireGuard server (10.0.0.99) is located on the private 10.0.0.0/24 network behind the NGFW.
Thus, when configuring WireGuard on the client (192.168.1.107), you would specify endpoint publicIP
, where publicIP is the the public IP address of the NGFW visible across the Internet. The NGFW must also be configured to accept WireGuard traffic on the port you specify and forward it internally to the server (10.0.0.99) using standard port forwarding or reverse proxy.
To force all client traffic to the server using WireGuard, you would specify allowed-ips 0.0.0.0/0
in the WireGuard configuration on the client (but still use allowed-ips 172.16.0.0/16
in the WireGuard configuration on the server). Any Internet requests sent to the server from the client on the VPN will be forwarded to the server’s default gateway (the NGFW) for relay to the Internet. Responses received by the server will then be sent to the client on the VPN.
Since the client is behind a NAT router (e.g. on a home network) and not directly visible to the server across the Internet, there is also no need to include the listen-port 55123
configuration on the client, nor the endpoint 192.168.1.107:55123
on the server. When the client contacts the server using WireGuard, the server will reply to the public IP address on the NAT router the client is behind.
Finally, to allow the server to forward IPv4 requests it receives from the client to other LANs, you must perform the following tasks on the server:
- Turn on IP routing using the command
sysctl -w net.ipv4.ip_forward=1 >> /etc/sysctl.conf
- Enable IP masquerading for requests from wg0 on the physical network interface connected to the DMZ LAN (e.g. eth0) using the command
iptables -A FORWARD -i wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
Of course, all of this configuration could also be added to /etc/wireguard/wg0.conf
on the client and server:
[root@client ~]# cat /etc/wireguard/wg0.conf
[Interface]
PrivateKey = 0EQpGsSfGwVRdxcCywG2ymnLG7mjmv+rB02UodcH10k=
Address = 172.16.0.1/16
DNS = 1.1.1.1 #By specifying your DNS configuration here, name resolution is faster
#No ListenPort needed!
[Peer]
PublicKey = cjmyZf4c+6U3pD2QT+6Bxkjj9qzU8EePjc8dSeuXvWs=
AllowedIPs = 0.0.0.0/0 #This forwards all traffic to the WireGuard server
Endpoint = publicIP:55234
[root@server ~]# cat /etc/wireguard/wg0.conf
[Interface]
PrivateKey = +FqYdSx+rIg2gwwyd3hCfap/1Vz3z2UuRZCPKKwMaXw=
Address = 172.16.0.99/16
ListenPort = 55234
PostUp = iptables -A FORWARD -i wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
[Peer]
PublicKey = 8pfWwwPK8R+Qe/fuN5FZ0P2ddngWd8s79sOQw5Q7SXE=
AllowedIPs = 172.16.0.0/16
#No Endpoint needed!
Method 2: Remote Access using a WireGuard server connected to the demarc
In the configuration shown below, the WireGuard server is connected directly to the demarc and obtains a public IP address, but has a second network interface connected to the DMZ (10.0.0.99). In this case, we call the WireGuard server an edge device as it sits on the edge of the corporate network.
The biggest difference between this configuration and the previous one is that when configuring WireGuard on the client (192.168.1.107), you would specify endpoint publicIP
, where publicIP is the the public IP address of the server connected to the demarc. Consequently, there’s no need to configure the NGFW to forward requests to 10.0.0.99.
All other configuration is identical to the previous example. However, recall that you must specify the network interface that is connected to the DMZ when configuring IP masquerading on the server. If this network is eth1 (since eth0 is connected to the demarc), you would need to run the iptables -A FORWARD -i wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o eth1 -j MASQUERADE
command on the server.
7. Using WireGuard to secure traffic between two (or more) networks
If you have WireGuard servers configured as edge devices in two (or more) locations, and each location uses unique private IP ranges (e.g. 10.1.x.0/24 and 10.2.x.0/24), you could use WireGuard to allow systems on the 10.1.x.0/24 networks to securely access the systems on the 10.2.x.0/24 networks, and vice versa. This is often called a site-to-site VPN.
The configuration would be very similar to our basic example at the beginning of this blog post. Instead of a client and a server, we could have server1 and server2, and each server would be configured as a router that performs masquerading. The /etc/wireguard/wg0.conf
configuration for these systems is shown below:
[root@server1 ~]# cat /etc/wireguard/wg0.conf
[Interface]
PrivateKey = 0EQpGsSfGwVRdxcCywG2ymnLG7mjmv+rB02UodcH10k=
Address = 172.16.1.99/16
ListenPort = 55123
PostUp = iptables -A FORWARD -i wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o eth1 -j MASQUERADE
PostDown = iptables -D FORWARD -i wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o eth1 -j MASQUERADE
[Peer]
PublicKey = cjmyZf4c+6U3pD2QT+6Bxkjj9qzU8EePjc8dSeuXvWs=
AllowedIPs = 10.2.0.0/16 #This forwards all traffic for 10.2.x.0/24 networks to server2
Endpoint = server2publicIP:55234
[root@server2 ~]# cat /etc/wireguard/wg0.conf
[Interface]
PrivateKey = +FqYdSx+rIg2gwwyd3hCfap/1Vz3z2UuRZCPKKwMaXw=
Address = 172.16.2.99/16
ListenPort = 55234
PostUp = iptables -A FORWARD -i wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o eth1 -j MASQUERADE
PostDown = iptables -D FORWARD -i wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o eth1 -j MASQUERADE
[Peer]
PublicKey = 8pfWwwPK8R+Qe/fuN5FZ0P2ddngWd8s79sOQw5Q7SXE=
AllowedIPs = 10.1.0.0/16 #This forwards all traffic for 10.1.x.0/24 networks to server1
Endpoint = server1publicIP:55123
It’s also important to note that many NGFWs have site-to-site VPNs built into them. I imagine that most of them will move to using WireGuard for this functionality over time for performance and security reasons!