How to set up a peer-to-peer fully-meshed VPN between servers

If you have deployed multiple servers on DigitalOcean or Vultr, you probably know that every server can be provided with a private IP address that is associated with the private network of the datacenter. This is great, because it allows to deploy distributed services (like databases) that communicate with each other with a latency that is close to zero.

The “issue” is that the private network is shared: that means that in the same subnet there are hundreds and possibly thousands of other servers. So at some point you’ll want to set up a Virtual Private Network (VPN) between your servers, so that they’ll be able to contact each other with custom private IP addresses (like 10.0.1.x) through an encrypted network.

Why you should use

  • it allows to bind the VPN to a custom interface, so you can create a network like 10.0.x.x/16 with all your hosts
  • it creates a fully-meshed peer-to-peer VPN network. That means that each of the hosts needs the IP address of just one another server (inside the VPN) to automatically discover all the nodes
  • the tunnel is of course encrypted, and AES-256 is supported
  • automatic reconnection when a connection is lost or a peer becomes temporarily unavailable
  • great performance
  • a pre-built .deb package is available
  • it’s open source
  • it’s incredibly easy to configure, you’ll be surprised
  • it’s stable. We’ve been running on 10 servers for almost a year, and we never had a crash or any issue. It just works.

I’m now going to explain how to configure a basic mesh P2P VPN network with

Let’s go

I’m assuming the servers are running Ubuntu, but the following steps should be the same for all the Debian-based Linux distributions. If you use a different type of Linux distribution, follow the steps in the readme for compiling.

Run these commands:

sudo dpkg -i vpncloud_0.8.1_amd64.deb

A systemd service will be automatically created, together with a sample configuration file.


The first thing you should do is to create a copy of the sample file.

sudo cp /etc/vpncloud/ /etc/vpncloud/
sudo nano /etc/vpncloud/

Now let’s take a look at the content of the configuration file. The changes I’m describing here should be made on all the servers where you installed the VPN.

As I previously said, creates a full mesh, but to do so it doesn’t need the IP addresses of all the servers. In my configuration, I have configured 3 peers, which are the IP addresses of the 3 database servers (they should never be offline at the same time). Every VPN instance is able to use those peers to create connections with all the other hosts in the virtual network.


- privateip1:3210
- privateip2:3210

Although it is possible to put the public IP addresses of the servers in the configuration, it is recommended that you use the internal private network IP addresses (provided by DigitalOcean for eth1, for example), for better performance and lower latency. You can also use hostnames, or even put “fake” hostnames that you can then map with the private IPs in the /etc/hosts file.

Also, make sure that the port 3210 is open on the internal network on all the servers. If you need a simple firewall that allows you to open a port only on one interface, take a look at FireHOL. It’s great!

Shared key

shared_key: "nT4gAGSP!S9!2Rjb9%h*gdVN*8NszP"

(*don’t use this key*)

This is the key that will be used to derive the symmetric key to be used with AES-256 for encrypting the VPN traffic. The advantage of using a shared key is that you don’t need to transfer complex keys between servers. It’s like a password, and it should be a string of ASCII characters. In my case, the shared key is a random string of 30 ASCII characters.


crypto: aes256

Virtual interface

ifup: "ifconfig $IFNAME mtu 1400"
ifdown: "ifconfig $IFNAME down"

According to the IP address (in CIDR notation) that you specify in the ifup command, the operating system will automatically understand that you’re going to create a network whose hosts are, in this case, in the subnet 10.x.x.x .

In my case I “grouped” the servers by changing the third octet of the address, to achieve a configuration like this:

  • 10.0.1.x reserved to database servers
  • 10.0.2.x reserved to web servers

Run boy run

sudo service vpncloud@myvpn start

where myvpn should be replaced with the name of the configuration file (excluding the .net extension).

Check for errors with sudo service vpncloud@myvpn status and then try to ping all the servers: on each server, ping , , etc., depending on you configuration. They should all work with a latency close to zero.

Finally, don’t forget to enable the service, so that it automatically runs at system startup.

sudo systemctl enable vpncloud@myvpn

Naming hosts db1.vpn db2.vpn

Wasn’t that easy?

If you liked as I did, star the repository on GitHub, and if you are able to find a bug, contribute by creating an issue or by sending a pull request (you’ll need to understand a bit of the Rust language to do that. I don’t).

Also, take a look at the wiki on GitHub if you’re interested in learning other aspects of the VPN.

Italy. Student at University of Trento.