This post will explain you in details how to setup your own v-Linux server on OpenVZ platform as VPN server with WireGuard VPN.
There are many tutorials about setting up your own WireGuard VPN, but only few cover aspect of installing it on the Linux VM which runs on top of OpenVZ (Virtuozzo hypervisor). And also this tutorial will cover following aspects:
Installation of WireGuard VPN (wireguard-go version).
Configuration which supports two different concentrators on the same server:
One for your private use another one for other users, e.g. your friends, colleagues, etc.
Or it can be two (or more) groups of users in your organization.
IPv4 and IPv6 configuration covering:
Forwarding and NAT
Isolation of clients: blocking of inter-group (communication between groups) and intra-group (communication between peers in the group) traffic.
DNS server configuration (Unbound).
Clients configuration.
I am using Ubuntu 18.04 LTS. I suppose that my instructions are suitable for most Debian family systems.
Installation of WireGuard VPN on v-Linux
Firs step is to check what hypervisor uses your cloud provider. You shall run the following command in the terminal:
1
systemd-detect-virt
If you do not see openvz, then most probably standard installation method shall be OK for you. If, like me, you see openvz ,then go to Method 2.
Method 1: Installation of WireGuard VPN as kernel module
I am using Strato.de for my virtual Linux server. Strato.de is using Virtuozzo as its cloud platform. The problem with Virtuozzo is that it is based on OpenVZ hypervisor and user cannot install additional Linux kernel modules in its VM. Strictly speaking, you can install some modules, but neither generic, nor kvm modules pass to my VM.
Therefore standard way of installation of WireGuard VPN is not possible and I had to use wireguard-go instead. Wireguard-go is written by the same team which writes WireGuard, but instead of Linux kernel space, this version of WireGuard runs in the user space.
When you need to update wireguard-go, you shall stop running wireguard service before copying new version of wireguard-go to the /usr/local/bin.
1
2
3
4
5
6
7
8
9
10
cd ~/source/wireguard-go
make
# you shall stop all your wireguard processes first.# I have two and I stop both of them.sudo systemctl stop wg-quick@wg0
sudo systemctl stop wg-quick@wg1
sudo cp wireguard-go /usr/local/bin
# start wireguard processes again.sudo systemctl start wg-quick@wg0
sudo systemctl start wg-quick@wg1
Configuration of server
Here I will explain how to configure WireGuard VPN server and clients for two groups of clients which/whom you want completely isolate from each other.
Enable IP forwarding
In the /etc/sysctl.conf file enable IPv4 and IPv6 forwarding.
1
2
3
4
5
6
7
# Uncomment the next line to enable packet forwarding for IPv4net.ipv4.ip_forward=1# Uncomment the next line to enable packet forwarding for IPv6# Enabling this option disables Stateless Address Autoconfiguration# based on Router Advertisements for this hostnet.ipv6.conf.all.forwarding=1
After update of the file, run the following command to apply changes to the system:
1
sysctl -p
Now you are good to go to the next steps and start WireGuard server configuration.
Configuration of WireGuard server
Generate server keys
At first we have to generate server keys. Because we are going to have two different listening interfaces, it is better to use different server keys.
Using root privileges:
1
2
3
4
5
6
7
8
mkdir /etc/wireguard/server-group-1
mkdir /etc/wireguard/server-group-2
cd /etc/wireguard
umask077wg genkey | tee server1_private_key | wg pubkey > server1_public_key
mv server1_* server-group-1/
wg genkey | tee server2_private_key | wg pubkey > server2_public_key
mv server2_* server-group-2/
IP addressing
Before we go to the configuration, I want to explain my IP addressing. IPv4 and IPv6 address pools are private IPv41 and Unique Local IPv62 respectively.
Client Group
IPv4 addresses pool
IPv6 addresses pool
Group 1
10.255.254.0/24
fd90:abcd:6789::/64
Group 2
10.255.0.0/24
fd98:cdef:89ab::/64
I know that /64 mask for IPv6 gives much larger space of addresses as /24 mask for IPv4 (2^64 vs 255), but I do this definition for simplicity of configuration. Realistictly saying, I am going to support maybe maximum 10 clients per group anyway, therefore this difference does not matter. But if you want to be more exact, then use /120 mask for IPv6 subnets when you use /24 IPv4 subnets.
In order to set up server for two groups of users, I am using two independent WireGuard VPN configuration files:
Unbound is included in the standard repo of Ubuntu, and is simply installed:
1
sudo apt-get install unbound unbound-host
DNS Server on VPN is not the authoritative DNS. Its role is to recursively forward client DNS requests to the upstream Root DNS servers. For this purpose we have to download list of Root DNS Servers on our server:
server:# 4 threads because I have 4 x vCPUs vLinux servernum-threads:4verbosity:1root-hints:"/var/lib/unbound/root.hints"# Server listens only on Loopback IPv4 and IPv6# and on WireGuard VPN private interfaces# to the clients (inside tunnels).interface:127.0.0.1interface:10.255.254.1interface:10.255.0.1interface:::1interface:fd90:abcd:6789::1interface:fd98:cdef:89ab::1max-udp-size:3072# Blocking access from any hostaccess-control:0.0.0.0/0 refuse# Allow access to loopback (for system)access-control:127.0.0.1allowaccess-control:::1allow# Allow access from VPN client subnetsaccess-control:10.255.254.0/24 allowaccess-control:10.255.0.0/24 allowaccess-control:fd90:abcd:6789::/64 allowaccess-control:fd98:cdef:89ab::/64 allowprivate-address:10.255.254.0/24private-address:10.255.0.0/24private-address:fd90:abcd:6789::/64private-address:fd98:cdef:89ab::/64hide-identity:yeshide-version:yesharden-glue:yesharden-dnssec-stripped:yesharden-referral-path:yesunwanted-reply-threshold:10000000val-log-level:1cache-min-ttl:60# I use short TTL for the cached recordscache-max-ttl:300prefetch:yesprefetch-key:yes
Configuration is ready and you can start DNS server:
You shall configure and start WireGuard VPN server on all necessary interfaces before you start Unbound, otherwise Unbound will not start and give you an error, because interfaces on which it shall listen on are not ready yet.
Configuration of iptables
I am not going to give all the theory about iptables, but you shall remember, that rules order matters!.
Initial configuration
Initially I have to block all unnecessary incoming traffic. And for this purpose I use following rules set.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
sudo iptables -A INPUT -i lo -j ACCEPT
sudo iptables -A INPUT -i wg0 -j ACCEPT
sudo iptables -A INPUT -i wg1 -j ACCEPT
sudo iptables -A INPUT -p icmp -m icmp --icmp-type 8 -m state --state NEW,RELATED,ESTABLISHED -j ACCEPT
sudo iptables -A INPUT -p tcp -m tcp --dport 22 -j ACCEPT
sudo iptables -A INPUT -p udp -m udp --dport 51820 -j ACCEPT
sudo iptables -A INPUT -p udp -m udp --dport 51821 -j ACCEPT
sudo iptables -A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
sudo iptables -A INPUT -j DROP
sudo ip6tables -A INPUT -i lo -j ACCEPT
sudo ip6tables -A INPUT -i wg0 -j ACCEPT
sudo ip6tables -A INPUT -i wg1 -j ACCEPT
sudo ip6tables -A INPUT -p ipv6-icmp -j ACCEPT
sudo ip6tables -A INPUT -p tcp -m tcp --dport 22 -j ACCEPT
sudo ip6tables -A INPUT -p udp -m udp --dport 51820 -j ACCEPT
sudo ip6tables -A INPUT -p udp -m udp --dport 51821 -j ACCEPT
sudo ip6tables -A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
sudo ip6tables -A INPUT -j DROP
Groups and users isolation
In order to isolate peers from traffic exchange, I use two different approaches simultaneously:
Blocking traffic using INPUT filters.
Blocking traffic using FORWARDING restrictions.
I use both of them because somehow one works for IPv4, but does not work for IPv6 and vice versa. Maybe it is a bug in iptables implementation or maybe it is normal behavior. I cannot figure this out, but implementation of them simultaneously gives results.
Isolation using FORWARDING rules is installed like this:
1
2
3
4
5
6
7
8
9
10
# For IPv4sudo iptables -A FORWARD -i wg1 -o wg1 -j REJECT --reject-with icmp-admin-prohibited
sudo iptables -A FORWARD -i wg0 -o wg0 -j REJECT --reject-with icmp-admin-prohibited
sudo iptables -A FORWARD -i wg1 -o wg0 -j REJECT --reject-with icmp-admin-prohibited
sudo iptables -A FORWARD -i wg0 -o wg1 -j REJECT --reject-with icmp-admin-prohibited
# For IPv6sudo ip6tables -A FORWARD -i wg1 -o wg1 -j REJECT --reject-with icmp6-adm-prohibited
sudo ip6tables -A FORWARD -i wg0 -o wg0 -j REJECT --reject-with icmp6-adm-prohibited
sudo ip6tables -A FORWARD -i wg0 -o wg1 -j REJECT --reject-with icmp6-adm-prohibited
sudo ip6tables -A FORWARD -i wg1 -o wg0 -j REJECT --reject-with icmp6-adm-prohibited
And then I also use isolating filters, which I apply on INPUT chain. For this I use my own rules chain called “wg-rules”.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
## For IPv4sudo iptables -N wg-rules
# Isolation of group subnets from each-othersudo iptables -A wg-rules -s 10.255.0.0/24 -d 10.255.254.0/24 -j DROP
sudo iptables -A wg-rules -s 10.255.254.0/24 -d 10.255.0.0/24 -j DROP
# Inserting this rule as the rule number 2.# Rule 1 in my configuration allows traffic on loopback.sudo iptables -I INPUT 2 -j wg-rules
## For IPv6sudo ip6tables -N wg-rules
# Isolation of group subnets from each-othersudo ip6tables -A wg-rules -s fd90:abcd:6789::/64 -d fd98:cdef:89ab::/64 -j DROP
sudo ip6tables -A wg-rules -s fd98:cdef:89ab::/64 -d fd90:abcd:6789::/64 -j DROP
# Inserting this rule as the rule number 2.# Rule 1 in my configuration allows traffic on loopback.sudo ip6tables -I INPUT 2 -j wg-rules
Listening on multiple ports
WireGuard VPN cannot listen on multiple ports. If you want your clients being able to connect on different ports, you shall use iptables for this.
E.g. server 1 listens on the UDP port 51820, and you want to open port 1194 (use OpenVPN port for WireGuard) in addition. For this case you shall add following configuration to iptables.
First for IPv4:
1
2
3
4
# <ext_int> is your external portsudo iptables -t nat -A PREROUTING -i <ext_int> -p udp -m udp --dport 1194 -j REDIRECT --to-ports 51820# <RULE NUMBER> shall be less than the last "-A INPUT -j DROP" rulesudo iptables -I INPUT <RULE NUMBER> -p udp -m udp --dport 1194 -j ACCEPT
And then for IPv6:
1
2
3
4
# <ext_int> is your external portsudo ip6tables -t nat -A PREROUTING -i <ext_int> -p udp -m udp --dport 1194 -j REDIRECT --to-ports 51820# <RULE NUMBER> shall be less than the last "-A INPUT -j DROP" rulesudo ip6tables -I INPUT <RULE NUMBER> -p udp -m udp --dport 1194 -j ACCEPT
As a result, your WireGuard VPN Server 1 will accept client connections on ports 1194 and 51820.
Summary example
My own iptables configuration looks like this (for IPv4):
Iptables rules will be cleaned with the next reboot. Therefore, in order to save them and recover after system reboot, I install iptables-persistent tool:
1
sudo apt-get install iptables-persistent
And when I modify iptables configuration, I save rules and they will be applied again after boot automatically.
Ideally, each client shall generate its own private key locally and then share with me only public key. In reality I create keys and configuration file for each client locally on the server. To clients on Android and iOS I am sending QR code which is generated from the client configuration file.
For the PC clients (Linux, Windows) I have to use other methods like USB, encrypted e-mail, etc.
I prefer to store clients keys and configurations in separate subfolders. I use subfolder per each VPN interface running.
Client keys are generated using the same command, which I use to generate server keys. E.g. for the client “Joe Doe” from Group 1:
1
2
3
4
cd /etc/wireguard/group-1-clients
wg genkey | tee Joe_Doe_private_key | wg pubkey > Joe_Doe_public_key
touch Joe_Doe.conf
chmod 600 Joe_Doe*
Joe Doe’s configuration file:
1
2
3
4
5
6
7
8
9
10
11
12
# Joe Doe's configuration file[Interface]Address = 10.255.254.2/32,fd90:abcd:6789::2/128
PrivateKey = <Joe Doe private key>
# DNS on server is usedDNS = 10.255.254.1,fd90:abcd:6789::1
[Peer]PublicKey = <Server 1 public key>
Endpoint = <Your Linux VM public IP>:51820
# All traffic is sent via VPN server.AllowedIPs = 0.0.0.0/0,::/0
Android, iOS
On Android and iOS devices you can use default WireGuard client:
In order to configure mobile clients, it is much more convenient to send client configurations as QR code pictures.
I installed qrencode tool:
1
sudo apt-get install qrencode
For the example above (Joe Doe config) you can generate QR code using following command:
1
qrencode -t ansiutf8 < Joe_Doe.conf
In the terminal you will see the QR code like this.
Linux
Linux clients can install WireGuard using instructions from WireGuard VPN web-site.
On Linux client PC configuration file is stored in the same /etc/wireguard folder.
You can start VPN connection with the following command:
1
sudo wg-quick up wg0
And you can check status of the connection with the command:
1
sudo wg show
Windows
Official WireGuard client for Windows is not very stable, and it is better to use TunSafe instead.
Use of this client is quite straightforward, You just import provided configuration file in the client, and you good to go.
Links to useful resources and other installation guides