Ryan Schachte's Blog
Create your own boring tunnel with Wireguard
January 7th, 2024

Tunneling has become the hot new thing on the web for things like security, self-hosting and exposing private services to the outside world. Tools like Tailscale and Cloudflare Tunnel help solve this problem, but in this article we are going to roll our own using a cheap VPS, Wireguard and a home server.

We will keep this setup minimal by using just Linux and Wireguard. If you haven’t read this article by my Matt, You may not need Cloudflare Tunnel. Linux is fine, then I recommend you start there.

Why boring tunnels?

So why even use tunnels at all? Tunnels allow us to access files, applications and services on our servers, networks or computers from anywhere in the world. This could range from large-scale enterprise networks to small scale media servers we want to share with our friends. Traditionally, port-forwarding your applications on your home router would suffice, but it opens up massive security holes into your home network. These security holes can give attackers open access to your private data.

What tunnels do?

Tunnels are interesting because they allow open streams of traffic into your network without having to worry too much about firewalls or NAT (network address translation). This works because the connection typically starts from inside the network you’re trying to secure and maintains a persistent connection between you and the remote machine acting as the guard outbound. Assuming you allow outbound connections for the UDP protocol, then opportunities to traffic things like screen sharing over VNC, SSH, HTTP, HTTPS and TCP become extremely simple. With some simple packet forwarding rules, we start to control anything and everything that enters and leaves our networks using a cheap VPS as the proxy.

Understanding the network

In our demo, we will expose a private web server on our personal laptop and expose it to the outside world via the VPS. Moving forward, I’ll assume you have a VPS you want to use as a proxy. This could be something as simple as a cheap $5/mo droplet on Digital Ocean.

Any machine would technically work here, but ideally you use something that hides your actual home IP from the outside world. You could even leverage a serverless function using auth headers to authenticate and forward requests.

The laptop will establish the connection to the VPS over the Wireguard port using UDP on port 51820. Asymmetric key pairs will be used to complete the handshake between the two machines and we will only whitelist the IPs of the VPS and laptop to keep out unwanted visitors.

Install Wireguard on both machines. Because I am using Linux in both cases, the following commands will assume Linux, but Wireguard runs on many platforms.

sudo apt-get update
sudo apt-get install wireguard

First navigate to /etc/wireugard and generate the keys for the client. In this case, the client is our Laptop.

wg genkey | tee privatekey | wg pubkey > publickey

Create a file we can use for the client config

touch /etc/wireguard/wg0.conf
sudo vim /etc/wireguard/wg0.conf
/etc/wireguard/wg0.conf
# Laptop

[Interface]
PrivateKey = <CLIENT_PRIVATE_KEY>

# this is the private IP you want to assign the client that the server will use to communicate over
Address = 10.0.0.1

# this represents the info of our VPS
[Peer]
PublicKey = <SERVER_PUBLIC_KEY>
Endpoint = <VPS_PUBLIC_IP_ADDRESS>:51820

# We are allowing IPs to reach us that match this address
AllowedIPs = 10.0.0.2

# Keeps the tunnel open
PersistentKeepalive = 25

Let’s swap over to the VPS and configure Wireguard on the server. The steps are almost identical aside from the config.

sudo apt-get update
sudo apt-get install wireguard
wg genkey | tee privatekey | wg pubkey > publickey
 
touch /etc/wireguard/wg0.conf
sudo vim /etc/wireguard/wg0.conf
/etc/wireguard/wg0.conf
# VPS

[Interface]
PrivateKey = <SERVER_PRIVATE_KEY>
Address = 10.0.0.2
ListenPort = 51820

# packet forwarding to allow forwarding packets between machines
PreUp = sysctl -w net.ipv4.ip_forward=1

# move packets over the network between the network interfaces
PreUp = iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 80 -j DNAT --to-destination 10.0.0.1:8080

PostDown = iptables -t nat -D PREROUTING -i eth0 -p tcp --dport 80 -j DNAT --to-destination 10.0.0.1:8080

PreUp = iptables -t nat -A POSTROUTING -o wg0 -j MASQUERADE
PostDown = iptables -t nat -D POSTROUTING -o wg0 -j MASQUERADE

# our laptop
[Peer]
PublicKey = <CLIENT_PUBLIC_KEY>
AllowedIPs = 10.0.0.1

Let’s break down some of this. The PREROUTING table will be hit before things are routed.

PreUp = iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 80 -j DNAT --to-destination 10.0.0.1:8080

In this case, we want to direct any inbound packets coming inbound from the outside world that target port 80. These packets will be routed to the private Wireguard IP of 10.0.0.1, which is our Laptop. These packets will travel over the Wireguard interface that gets created from the wg0.conf file.

Port :8080 represents a web server that runs on the Laptop. We will configure a test server toward the end of the article to validate this.

The next piece is the masquerading logic. PostDown is just cleanup to delete anything when we bring the interface down, so let’s look at PreUp.

PreUp = iptables -t nat -A POSTROUTING -o wg0 -j MASQUERADE

This command adds a rule to the POSTROUTING chain of the nat table in iptables. The -o wg0 option specifies that the rule applies to traffic going out on the wg0 interface, which is typically the WireGuard VPN interface. The -j MASQUERADE option instructs iptables to mask the source IP address of the outgoing packets with the IP address of the wg0 interface. This is known as Source Network Address Translation (SNAT), and it allows the VPN clients to communicate with servers on the internet as if the traffic originated from the VPN server itself

Security

We need to open some ports as most VPS’s will keep things closed off by default. The VPS needs to open the Wireguard port specified in the config so our laptop can connect to it.

sudo ufw allow 51820/udp

Let’s also open up HTTP to test exposing our HTTP server.

sudo ufw allow 80/tcp 

Connection establishment

On both machines, you should run Wireguard with sudo wg-quick up wg0. You can validate that the handshake was successful with sudo wg.

interface: wg0
  public key: (hidden)
  private key: (hidden)
  listening port: 58895
 
peer: (hidden)
  endpoint: (redacted):51820
  allowed ips: 10.0.0.2/32
  latest handshake: 1 minute, 7 seconds ago
  transfer: 145.34 KiB received, 231.90 KiB sent
  persistent keepalive: every 25 seconds

The latest handshake should be present which signifies successful communication.

You can use python to run a basic server out of a directory.

mkdir ~/test && echo "hello world" > ~/test/index.html && cd ~/test
python3 -m http.server 8080

This will run a file server from ~/test and host it on port 8080. Because the server is directed to forward inbound packets from the public interface to the laptop on 10.0.0.1:8080, you should be able to hit the server on http://<VPS_PUBLIC_IP> and see hello world.

Care to comment?