WireGuard VPN setup on v-Linux (OpenVZ)
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:
1systemd-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
Standard installation of WireGuard VPN is described on the WireGuard VPN web-site.
Method 2: Installation of wireguard-go
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.
To install wireguard-go I use following guide - WireGuard on OpenVZ/LXC
Update of the wireguard-go later
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
.
1cd ~/source/wireguard-go
2make
3# you shall stop all your wireguard processes first.
4# I have two and I stop both of them.
5sudo systemctl stop wg-quick@wg0
6sudo systemctl stop wg-quick@wg1
7sudo cp wireguard-go /usr/local/bin
8# start wireguard processes again.
9sudo systemctl start wg-quick@wg0
10sudo 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# Uncomment the next line to enable packet forwarding for IPv4
2net.ipv4.ip_forward=1
3
4# Uncomment the next line to enable packet forwarding for IPv6
5# Enabling this option disables Stateless Address Autoconfiguration
6# based on Router Advertisements for this host
7net.ipv6.conf.all.forwarding=1
After update of the file, run the following command to apply changes to the system:
1sysctl -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:
1mkdir /etc/wireguard/server-group-1
2mkdir /etc/wireguard/server-group-2
3cd /etc/wireguard
4umask 077
5wg genkey | tee server1_private_key | wg pubkey > server1_public_key
6mv server1_* server-group-1/
7wg genkey | tee server2_private_key | wg pubkey > server2_public_key
8mv 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:
- wg0.conf for Group 1
- wg1.conf for Group 2
I place them in the /etc/wireguard folder.
My wg0.conf:
1# wg0.conf
2[Interface]
3Address = 10.255.254.1/32,fd90:abcd:6789::1/128
4SaveConfig = false
5# PostUp option installs necessary iptables rules
6# for NAT and NAT66 after services is up.
7PostUp = iptables -t nat -A POSTROUTING -o venet0 -s 10.255.254.0/24 -j MASQUERADE; ip6tables -t nat -A POSTROUTING -o venet0 -s fd90:abcd:6789::/64 -j MASQUERADE
8# PostDown option removes iptables rules
9# for NAT and NAT66 when service is down.
10PostDown = iptables -t nat -D POSTROUTING -o venet0 -s 10.255.254.0/24 -j MASQUERADE; ip6tables -t nat -D POSTROUTING -o venet0 -s fd90:abcd:6789::/64 -j MASQUERADE
11ListenPort = 51820
12PrivateKey = <Server 1 private key>
13
14# Client 1 - Joe Doe
15[Peer]
16PublicKey = <Client 1 public key>
17AllowedIPs = 10.255.254.2/32,fd90:abcd:6789::2/128
18
19# Client 2 - John Smith
20[Peer]
21PublicKey = <Client 2 public key>
22AllowedIPs = 10.255.254.3/32,fd90:abcd:6789::3/128
My wg1.conf:
1# wg1.conf
2[Interface]
3Address = 10.255.0.1/32,fd98:cdef:89ab::1/128
4SaveConfig = false
5# PostUp option installs necessary iptables rules
6# for NAT and NAT66 after services is up.
7PostUp = iptables -t nat -A POSTROUTING -o <ext_int> -s 10.255.0.0/24 -j MASQUERADE; ip6tables -t nat -A POSTROUTING -o <ext_int> -s fd98:cdef:89ab::/64 -j MASQUERADE
8# PostDown option removes iptables rules
9# for NAT and NAT66 when service is down.
10PostDown = iptables -t nat -D POSTROUTING -o <ext_int> -s 10.255.0.0/24 -j MASQUERADE; ip6tables -t nat -D POSTROUTING -o <ext_int> -s fd98:cdef:89ab::/64 -j MASQUERADE
11ListenPort = 51821
12PrivateKey = <Server 2 private key>
13
14# Client 1 - Koos van der Merwe
15[Peer]
16PublicKey = <Client 1 public key>
17AllowedIPs = 10.255.0.2/32,fd98:cdef:89ab::2/128
18
19# Client 2 - Hugo Jedermann
20[Peer]
21PublicKey = <Client 2 public key>
22AllowedIPs = 10.255.0.3/32,fd98:cdef:89ab::3/128
Replace
<ext_int>
with the name of the external interface on your Linux VM. On my Linux VM this name isvenet0
.
DNS server configuration
As DNS server on my Linux VM I am using Unbound3.
Unbound is included in the standard repo of Ubuntu, and is simply installed:
1sudo 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:
1curl -o /var/lib/unbound/root.hints https://www.internic.net/domain/named.cache
2chown -R unbound:unbound /var/lib/unbound
In the /etc/unbound/unbound.conf.d/ folder I created my own configuration file named unbound_srv.conf with the following content:
1server:
2 # 4 threads because I have 4 x vCPUs vLinux server
3 num-threads: 4
4 verbosity: 1
5 root-hints: "/var/lib/unbound/root.hints"
6
7 # Server listens only on Loopback IPv4 and IPv6
8 # and on WireGuard VPN private interfaces
9 # to the clients (inside tunnels).
10 interface: 127.0.0.1
11 interface: 10.255.254.1
12 interface: 10.255.0.1
13 interface: ::1
14 interface: fd90:abcd:6789::1
15 interface: fd98:cdef:89ab::1
16 max-udp-size: 3072
17
18 # Blocking access from any host
19 access-control: 0.0.0.0/0 refuse
20 # Allow access to loopback (for system)
21 access-control: 127.0.0.1 allow
22 access-control: ::1 allow
23 # Allow access from VPN client subnets
24 access-control: 10.255.254.0/24 allow
25 access-control: 10.255.0.0/24 allow
26 access-control: fd90:abcd:6789::/64 allow
27 access-control: fd98:cdef:89ab::/64 allow
28
29 private-address: 10.255.254.0/24
30 private-address: 10.255.0.0/24
31 private-address: fd90:abcd:6789::/64
32 private-address: fd98:cdef:89ab::/64
33
34 hide-identity: yes
35 hide-version: yes
36
37 harden-glue: yes
38 harden-dnssec-stripped: yes
39 harden-referral-path: yes
40
41 unwanted-reply-threshold: 10000000
42
43 val-log-level: 1
44 cache-min-ttl: 60
45 # I use short TTL for the cached records
46 cache-max-ttl: 300
47 prefetch: yes
48 prefetch-key: yes
Configuration is ready and you can start DNS server:
1sudo systemctl enable unbound
2sudo systemctl start unbound
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.
1sudo iptables -A INPUT -i lo -j ACCEPT
2sudo iptables -A INPUT -i wg0 -j ACCEPT
3sudo iptables -A INPUT -i wg1 -j ACCEPT
4sudo iptables -A INPUT -p icmp -m icmp --icmp-type 8 -m state --state NEW,RELATED,ESTABLISHED -j ACCEPT
5sudo iptables -A INPUT -p tcp -m tcp --dport 22 -j ACCEPT
6sudo iptables -A INPUT -p udp -m udp --dport 51820 -j ACCEPT
7sudo iptables -A INPUT -p udp -m udp --dport 51821 -j ACCEPT
8sudo iptables -A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
9sudo iptables -A INPUT -j DROP
10
11sudo ip6tables -A INPUT -i lo -j ACCEPT
12sudo ip6tables -A INPUT -i wg0 -j ACCEPT
13sudo ip6tables -A INPUT -i wg1 -j ACCEPT
14sudo ip6tables -A INPUT -p ipv6-icmp -j ACCEPT
15sudo ip6tables -A INPUT -p tcp -m tcp --dport 22 -j ACCEPT
16sudo ip6tables -A INPUT -p udp -m udp --dport 51820 -j ACCEPT
17sudo ip6tables -A INPUT -p udp -m udp --dport 51821 -j ACCEPT
18sudo ip6tables -A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
19sudo 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# For IPv4
2sudo iptables -A FORWARD -i wg1 -o wg1 -j REJECT --reject-with icmp-admin-prohibited
3sudo iptables -A FORWARD -i wg0 -o wg0 -j REJECT --reject-with icmp-admin-prohibited
4sudo iptables -A FORWARD -i wg1 -o wg0 -j REJECT --reject-with icmp-admin-prohibited
5sudo iptables -A FORWARD -i wg0 -o wg1 -j REJECT --reject-with icmp-admin-prohibited
6# For IPv6
7sudo ip6tables -A FORWARD -i wg1 -o wg1 -j REJECT --reject-with icmp6-adm-prohibited
8sudo ip6tables -A FORWARD -i wg0 -o wg0 -j REJECT --reject-with icmp6-adm-prohibited
9sudo ip6tables -A FORWARD -i wg0 -o wg1 -j REJECT --reject-with icmp6-adm-prohibited
10sudo 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## For IPv4
2sudo iptables -N wg-rules
3# Isolation of group subnets from each-other
4sudo iptables -A wg-rules -s 10.255.0.0/24 -d 10.255.254.0/24 -j DROP
5sudo iptables -A wg-rules -s 10.255.254.0/24 -d 10.255.0.0/24 -j DROP
6# Inserting this rule as the rule number 2.
7# Rule 1 in my configuration allows traffic on loopback.
8sudo iptables -I INPUT 2 -j wg-rules
9
10## For IPv6
11sudo ip6tables -N wg-rules
12# Isolation of group subnets from each-other
13sudo ip6tables -A wg-rules -s fd90:abcd:6789::/64 -d fd98:cdef:89ab::/64 -j DROP
14sudo ip6tables -A wg-rules -s fd98:cdef:89ab::/64 -d fd90:abcd:6789::/64 -j DROP
15# Inserting this rule as the rule number 2.
16# Rule 1 in my configuration allows traffic on loopback.
17sudo 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# <ext_int> is your external port
2sudo iptables -t nat -A PREROUTING -i <ext_int> -p udp -m udp --dport 1194 -j REDIRECT --to-ports 51820
3# <RULE NUMBER> shall be less than the last "-A INPUT -j DROP" rule
4sudo iptables -I INPUT <RULE NUMBER> -p udp -m udp --dport 1194 -j ACCEPT
And then for IPv6:
1# <ext_int> is your external port
2sudo ip6tables -t nat -A PREROUTING -i <ext_int> -p udp -m udp --dport 1194 -j REDIRECT --to-ports 51820
3# <RULE NUMBER> shall be less than the last "-A INPUT -j DROP" rule
4sudo 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):
1sudo iptables -S
2-P INPUT ACCEPT
3-P FORWARD ACCEPT
4-P OUTPUT ACCEPT
5-N wg-rules
6
7## INPUT rules
8# Allow traffic on loopback
9-A INPUT -i lo -j ACCEPT
10# WireGuard rules chain
11-A INPUT -j wg-rules
12-A INPUT -i wg0 -j ACCEPT
13-A INPUT -i wg1 -j ACCEPT
14-A INPUT -p icmp -m icmp --icmp-type 8 -m state --state NEW,RELATED,ESTABLISHED -j ACCEPT
15# Allow SSH
16-A INPUT -p tcp -m tcp --dport 22 -j ACCEPT
17-A INPUT -p udp -m udp --dport 1194 -j ACCEPT
18-A INPUT -p udp -m udp --dport 1195 -j ACCEPT
19-A INPUT -p udp -m udp --dport 51820 -j ACCEPT
20-A INPUT -p udp -m udp --dport 51821 -j ACCEPT
21-A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
22-A INPUT -j DROP
23
24## Forwarding rules
25-A FORWARD -i wg1 -o wg1 -j REJECT --reject-with icmp-admin-prohibited
26-A FORWARD -i wg0 -o wg0 -j REJECT --reject-with icmp-admin-prohibited
27-A FORWARD -i wg1 -o wg0 -j REJECT --reject-with icmp-admin-prohibited
28-A FORWARD -i wg0 -o wg1 -j REJECT --reject-with icmp-admin-prohibited
29
30## WireGuard rules chain definition
31# Isolation of group subnets from each-other
32-A wg-rules -s 10.255.0.0/24 -d 10.255.254.0/24 -j DROP
33-A wg-rules -s 10.255.254.0/24 -d 10.255.0.0/24 -j DROP
34
35## NAT rules
36sudo iptables -t nat -S
37-P PREROUTING ACCEPT
38-P INPUT ACCEPT
39-P OUTPUT ACCEPT
40-P POSTROUTING ACCEPT
41# Port redirectoin
42-A PREROUTING -i venet0 -p udp -m udp --dport 1194 -j REDIRECT --to-ports 51820
43-A PREROUTING -i venet0 -p udp -m udp --dport 1195 -j REDIRECT --to-ports 51821
44
45# Dynamic NAT rules instaled by the wg-quick service
46-A POSTROUTING -s 10.255.254.0/24 -o venet0 -j MASQUERADE
47-A POSTROUTING -s 10.255.0.0/24 -o venet0 -j MASQUERADE
For IPv6 rules configuration is similar.
Persistence of iptables rules
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:
1sudo apt-get install iptables-persistent
And when I modify iptables configuration, I save rules and they will be applied again after boot automatically.
1sudo bash -c 'iptables-save > /etc/iptables/rules.v4'
2sudo bash -c 'ip6tables-save > /etc/iptables/rules.v6'
Clients
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.
Using root privileges:
1mkdir /etc/wireguard/group-1-clients
2mkdir /etc/wireguard/group-2-clients
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:
1cd /etc/wireguard/group-1-clients
2wg genkey | tee Joe_Doe_private_key | wg pubkey > Joe_Doe_public_key
3touch Joe_Doe.conf
4chmod 600 Joe_Doe*
Joe Doe’s configuration file:
1# Joe Doe's configuration file
2[Interface]
3Address = 10.255.254.2/32,fd90:abcd:6789::2/128
4PrivateKey = <Joe Doe private key>
5# DNS on server is used
6DNS = 10.255.254.1,fd90:abcd:6789::1
7
8[Peer]
9PublicKey = <Server 1 public key>
10Endpoint = <Your Linux VM public IP>:51820
11# All traffic is sent via VPN server.
12AllowedIPs = 0.0.0.0/0,::/0
Android, iOS
On Android and iOS devices you can use default WireGuard client:
- For Android: Google Play Store
- For iOS: Apple Store
In order to configure mobile clients, it is much more convenient to send client configurations as QR code pictures. I installed qrencode tool:
1sudo apt-get install qrencode
For the example above (Joe Doe config) you can generate QR code using following command:
1qrencode -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:
1sudo wg-quick up wg0
And you can check status of the connection with the command:
1sudo 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.