This article was last updated on December 28, 2023, 1 month ago. The content may be out of date.

WireGuard is a modern VPN that utilizes state-of-art cryptography. It’s very easy to set up compared to other VPNs. In this post we are exploring how to set up secure networks between two servers over the internet.


First, we will need the necessary components: wireguard-go and wg from wireguard-tools. wireguard-go is used to create the network interface that the system uses to create the tunnel between the servers. wg manages the configuration of the interfaces.


This post uses wg instead of wg-quick, which supports more options than wg. wg-quick is a simple wrapper of wg that supports a few common use cases. It can be downloaded in the wireguard-tools repo.


We choose wireguard-go instead of the WireGuard kernel module because it’s easy to set up and has no runtime dependency. Some of the virtual environments I worked with didn’t support installing WireGuard kernel modules. In theory this will lead to performance loss, but I haven’t noticed any. In any case, wg is compatible with both kinds of setup.

Setting up interfaces

Setting up interfaces is easy with an administrator account. Simply run:

wireguard wg0

and it’s done.

Configuring WireGuard

Now that the interfaces are created, we need to configure WireGuard itself. First let’s generate the keys used by WireGuard:

wg genkey | tee /dev/tty | wg pubkey

We get two lines: the first is the private key, and the second is the corresponding public key.


We need to create keys for all the servers we need to configure and note them down.

Next we begin writing the configuration file for each of the servers. Let’s take a look at the example provided by the manual:

PrivateKey = yAnz5TF+lXXJte14tji3zlMNq+hd2rYUIgJBgB3fBmk=
ListenPort = 51820

PublicKey = xTIBA5rboUvnH4htodjb6e697QjLERt1NAB4mZqp8Dg=
Endpoint =
AllowedIPs =,

PublicKey = TrMvSoP4jYQlY6RIzBgbssQqY3vxI2Pi+y71lOWWXX0=
Endpoint = [2607:5300:60:6b0::c05f:543]:2468
AllowedIPs =,

PublicKey = gN65BkIKy1eCE9pP1wdc8ROUtkHLF2PfAqYdyYBz6EA=
Endpoint =
AllowedIPs =

[Interface] configures the interface on this server, we fill the PrivateKey using the key generated before. We can omit the ListenPort part, but we can’t specify its address in the other server’s configuration.

[Peer] configures the peers this server can communicate with. The public key generated from the other server goes into the PublicKey field. Endpoint specify the other server’s WireGuard endpoint. It can be omitted just like ListenPort. AllowedIPs specifies from what addresses traffic from this peer are allowed and outgoing traffic with those addresses will be directed to this peer. For simplicity, both of these servers are using the same LAN IP range


Although WireGuard will automatically update its peer information, first one of the peers need to be able to contact the other peer. This can happen when one of the peer has at least one public IP address but the other is behind NAT. The communication must be initiated by the peer behind NAT.

AllowdIPs doesn’t configure the routing used by the actual server network stacks. We’ll need to configure it separately.

After finishing the configuration file, we can use them by running:

wg wg setconf wg0 ${path to the configuration file}

Setting up the network interface

Finally, we need to set up the interface so that WireGuard can be used by the server network.

We’ll need to specify different LAN IP addresses for these servers. These addresses need to satisfy the AllowedIPs from before. They need to be different for these servers.

For example, one of the server will use the IP address We run:

ip address add dev wg0
ip link set up dev wg0

Do the same, but with another IP address for the other server. Now we have a working WireGuard network between the two servers.


This setup is used to create a secure network between servers. If we want to forward traffic to another server, IP packet filter rules need to be created and several of these options need to be modified.