In this post, I will show you how we can connect two peers (any two servers/instances) via Wireguard. As long as at least one of the peer is able to connect directly to the other one, this should work even if one of the peer is behind a NAT/firewall that blocks incoming connections.

In this post, I will connect a local Virtual Machine to a remote Cloud VM via Wireguard.

Towards the end of the post, I will also show you how you can route the client’s internet traffic via the CloudVM through Wireguard

I have a YouTube video with live demo, if you are interested

Connecting local machine to a remote Cloud VM Link to heading

For the sake of simplicity, let us call the Cloud VM the server and the local laptop the client.

Setting up the server Link to heading

First, let us setup the Cloud VM. In my case, it is running in DigitalOcean. The server has Debian 12.

If you are not familiar with setting up an instance in DigitalOcean, their docs are pretty easy. Check it out HERE

And you can choose these options

  • Size : choose the smallest possible under “shared cpu”. Should be around $5/month
  • Additional storage: You don’t need any
  • Authentication method: Choose SSH key
  • Region: Choose what is closest to you
  • Operating system : Debian 12 (or whatever the latest version of Debian is)

Server: Installing Wireguard Link to heading

Since we are using Debian 12, we can install Wireguard using apt

sudo apt update
sudo apt install wireguard

Server: Configuring Link to heading

Generating keys Link to heading

wg genkey | sudo tee /etc/wireguard/privatekey | wg pubkey | sudo tee /etc/wireguard/publickey

Removing unnecessary permissions from the private key

sudo chmod 400 /etc/wireguard/privatekey

Creating the config file Link to heading

So, on the server, let us create a new config file using your favourite text editor

sudo vim /etc/wireguard/wg0.conf

And add the below

[Interface]
Address = 10.0.0.1/24
PrivateKey = [Server's Private Key]
ListenPort = 51820

Make sure to replace the PrivateKey with the privatekey we just created, which is located at /etc/wireguard/privatekey

Setting up the client Link to heading

In my case, the client is also using Debian 12

mansoor@debian:~$ sudo apt update
mansoor@debian:~$ sudo apt install wireguard

Client : Configuring Link to heading

Generating keys Link to heading

wg genkey | sudo tee /etc/wireguard/privatekey | wg pubkey | sudo tee /etc/wireguard/publickey

Removing unnecessary permissions from the private key

sudo chmod 400 /etc/wireguard/privatekey

Creating the config file Link to heading

Create a file /etc/wireguard/wg0.conf with the config like below

[Interface]
PrivateKey = [Client's Private Key]
Address = 10.0.0.2/32

[Peer]
PublicKey = [Server's Public Key]
Endpoint = [Server's Public IP]:51820
AllowedIPs = 10.0.0.1/32
PersistentKeepalive = 25

Ensure the following

  • You are replacing the client’s private key correctly in the config file
  • You are replacing the server’s PUBLIC KEY correctly in the config file. You need to copy it from the server we generated in the previous step
  • You are replacing Endpoint with the server’s public IP.

Server : Updating the server with client public key Link to heading

This is very important

On the server, now we need to tell it the public key of the client. Edit the /etc/wireguard/wg0.conf on the server and add the following

Make sure you place the public key of the client

[Peer]
PublicKey = [Public key of the client]
AllowedIPs = 10.0.0.2/32 # This is the IP we gave the client

Server: Bringing it up Link to heading

We can bring up our VPN server using wg-quick. Remember, the name here wg0 needs to match the name of the config file, that is, wg0.conf.

We can also enable it to start on boot using systemd

sudo systemctl start wg-quick@wg0
# enable it on boot
sudo systemctl enable wg-quick@wg0

Client : Bringing it up Link to heading

Once the configs are in place, we can start our tunnel with

sudo systemctl start wg-quick@wg0
sudo systemctl enable wg-quick@wg0

Check that it has started

sudo systemctl status wg-quick@wg0

Verifying the connection Link to heading

On the server Link to heading

We can use sudo wg show to see the status

mansoor@demo:~$ sudo wg show
interface: wg0
  public key: +SvTJijfLvluSdBwM5o8OZMBBkTA2OCjAxIl6Ms2zTU=
  private key: (hidden)
  listening port: 51820

peer: mBYIhwX3YCO5CxPwfbhGR7rRaKAddm/C3aGQZ4VT/3A=
  endpoint: <public ip of my home network>:52857
  allowed ips: 10.0.0.2/32
  latest handshake: 54 seconds ago
  transfer: 2.36 KiB received, 1.52 KiB sent
mansoor@demo:~$

We can also ping the client from the server

mansoor@demo:~$ ping -c 2 10.0.0.2
PING 10.0.0.2 (10.0.0.2) 56(84) bytes of data.
64 bytes from 10.0.0.2: icmp_seq=1 ttl=64 time=16.3 ms
64 bytes from 10.0.0.2: icmp_seq=2 ttl=64 time=8.21 ms

--- 10.0.0.2 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1002ms
rtt min/avg/max/mdev = 8.212/12.261/16.310/4.049 ms

We can see that the server is able to reach the client over the VPN tunnel

On the client Link to heading

mansoor@debian:~$ sudo wg show
interface: wg0
  public key: mBYIhwX3YCO5CxPwfbhGR7rRaKAddm/C3aGQZ4VT/3A=
  private key: (hidden)
  listening port: 52857

peer: +SvTJijfLvluSdBwM5o8OZMBBkTA2OCjAxIl6Ms2zTU=
  endpoint: 138.197.67.72:51820   # that is the public ip of my server
  allowed ips: 10.0.0.1/32
  latest handshake: 52 seconds ago
  transfer: 2.98 KiB received, 5.99 KiB sent
  persistent keepalive: every 25 seconds
mansoor@debian:~$

Let’s try a ping as well

mansoor@debian:~$ ping -c 2 10.0.0.1
PING 10.0.0.1 (10.0.0.1) 56(84) bytes of data.
64 bytes from 10.0.0.1: icmp_seq=1 ttl=64 time=14.5 ms
64 bytes from 10.0.0.1: icmp_seq=2 ttl=64 time=16.7 ms

--- 10.0.0.1 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1002ms
rtt min/avg/max/mdev = 14.468/15.582/16.696/1.114 ms
mansoor@debian:~$

Great, that works too.

[ONLY IF YOU NEED IT] Sending the client’s internet traffic via Wireguard Link to heading

So far we created a “tunnel” between our local VM and a remote server.

If you want to route all the traffic from the client via the Wireguard VPN (so that it will go out to the internet via your CloudVM), we need to make some changes.

On the server Link to heading

You will need to edit /etc/sysctl.conf to ensure net.ipv4.ip_forward=1 is uncommented. and apply the changes with

sudo sysctl -p

Server : Stop Wireguard Link to heading

Note: This is important! Stop wireguard first on the server before proceeding

 sudo systemctl stop wg-quick@wg0

Server: Update the config Link to heading

And update the wg0.conf on the server. and add the following under the [Interface]

PostUp = iptables -t nat -I POSTROUTING -o eth0 -j MASQUERADE
PostUp = iptables -A FORWARD -i %i -j ACCEPT
PostUp = iptables -A FORWARD -o %i -j ACCEPT
PreDown = iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
PreDown = iptables -D FORWARD -i %i -j ACCEPT
PreDown = iptables -D FORWARD -o %i -j ACCEPT

so, on the server, the full config looks something like this

[Interface]
Address = 10.0.0.1/24
ListenPort = 51820
PrivateKey = eWEgc25lYWt5LCB3YXRjaGEgZG9pbiBoZXJlPz8/

PostUp = iptables -t nat -I POSTROUTING -o eth0 -j MASQUERADE
PostUp = iptables -A FORWARD -i %i -j ACCEPT
PostUp = iptables -A FORWARD -o %i -j ACCEPT
PreDown = iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
PreDown = iptables -D FORWARD -i %i -j ACCEPT
PreDown = iptables -D FORWARD -o %i -j ACCEPT

[Peer]
PublicKey = mBYIhwX3YCO5CxPwfbhGR7rRaKAddm/C3aGQZ4VT/3A=
AllowedIPs = 10.0.0.2/32

and finally restart

 sudo systemctl restart wg-quick@wg0

On the client Link to heading

All we have to do is edit the AllowedIPs under /etc/wireguard/wg0.conf

Make it so that :

AllowedIPs = 0.0.0.0/0  # Send all traffic via Wireguard

The full config looks like this

mansoor@debian:~$ sudo cat /etc/wireguard/wg0.conf
[Interface]
PrivateKey = aHR0cHM6Ly95b3V0dS5iZS9kUXc0dzlXZ1hjUQ==
Address = 10.0.0.2/32

[Peer]
PublicKey = +SvTJijfLvluSdBwM5o8OZMBBkTA2OCjAxIl6Ms2zTU=
Endpoint = 138.197.67.72:51820
AllowedIPs = 0.0.0.0/0
PersistentKeepalive = 25

mansoor@debian:~$

Note: go ahead and hack me with those exposed private keys :(

Restart WireGuard

sudo systemctl restart wg-quick@wg0

Verify that the connection is working

mansoor@debian:~$ sudo wg show
[sudo] password for mansoor:
interface: wg0
  public key: mBYIhwX3YCO5CxPwfbhGR7rRaKAddm/C3aGQZ4VT/3A=
  private key: (hidden)
  listening port: 37044
  fwmark: 0xca6c

peer: +SvTJijfLvluSdBwM5o8OZMBBkTA2OCjAxIl6Ms2zTU=
  endpoint: 138.197.67.72:51820
  allowed ips: 0.0.0.0/0
  latest handshake: 1 minute, 59 seconds ago
  transfer: 2.27 KiB received, 5.12 KiB sent
  persistent keepalive: every 25 seconds

Testing internet access Link to heading

At this point, the client VM is sending all the traffic via the VPN (the CloudVM) We can verify this by checking the client’s public IP

mansoor@debian:~$ curl ip.esc.sh
Your IP : 138.197.67.72
mansoor@debian:~$

As you can see, the client VM now have the server’s public IP, which means we are tunneling all the traffic via the CloudVM