How I use rathole as an alternative to ngrok

Using rathole with opentofu to automatically create/destroy https tunnels hosted on digitaocean

  ยท   4 min read

TL;DR

I setup a tofu config to create a digitalocean droplet, And a some python/shell scripts to download/configure and run, rathole (for the tunnel), and caddy (for the ssl certs). Each time I run the command, I get a new ephemeral subdomain and droplet, and once I run the destroy command. The server and the domain are deleted

You can find the code for this on github

The Problem

I was working on a side project one day (maybe I will write about it when it’s done), And I needed to expose the web server I was working on.

I decided to use ngrok as the tunnel, But when I was testing some feature of the app, I kept running into the rate limits of ngrok, And since I did not feel like paying for ngrok, I decided to use something else

I heard of rathole (a self hosted alternative to ngrok), And I thought to myself; using a $6/month ($0.008930/hour) digitalocean droplet, I could expose my server to the internet and be extremely cheap.

So the plan was to create a digitalocean droplet, run the rathole tunnel, and destroy the server when i was done

First Try

I created the droplet, downloaded the rathole binary, and configured the client and server with the same secret key. This worked flawlessly, I was quite happy with the low latency.

Now the only issue was I didn’t want to have to create and install this server every time I want to expose my app. (remember it’s only cheap if I destroy the server with I am not using it)

Automation begins

Since I know I could automate the creating and deletion of the server using Terraform, And I since never tried to use opentofu, I decided to give opentofu a try.

The goal was to create the smallest server ($6/month) in the TOR1 region (I am from Montreal, TOR1 is in Toronto)

I ended up this something like this

resource "digitalocean_droplet" "rathole" {
    image  = "ubuntu-24-04-x64"
    region = "TOR1"
    name   = "rathole"
    size   = "s-1vcpu-1gb"
    ...
}

Then I could run tofu apply to automatically create the droplet for me.

However I still need to manually download the binary, copy over the configuration and

How do I get HTTPS?

For the testing I was doing I needed https (not self signed), and since rathole is only a tunnel, it can’t create/use SSL certs.

What I landed on was, to use a caddy to reverse proxy the rathole server, This would mean that the rathole server would listen on 127.0.0.1:8080 and then caddy would listen on 0.0.0.0:80/443. Caddy would automatically generate and verify (using http challenge) the ssl cert.

Domain name

Since I will now be using a ssl cert, I will need a domain name. But since one of the goals of this project is to start/stop the server quickly, If I use the same domain; the DNS cache will be messed up. This is because each digitalocean droplet will have a different ipv4 address. I looked into reserving an address, But this will have a monthly ongoing cost. And since I am looking to save the most amount of money, Paying monthly for an address is out of the question

So the only option I have left is to create a new subdomain for each deployment. Turns out this is quite easy, All I need is to write some terraform

# genrate a random string
resource "random_string" "domain_name" {
  length  = 12
  lower   = true
  special = false
  numeric = false
  upper   = false
}

# create a new domain
resource "digitalocean_domain" "rathole_domain" {
  # use the random string from before
  name       = "rathole-${random_string.domain_name.id}.do.mendy.dev"
  # use the ip address from the created droplet
  ip_address = digitalocean_droplet.rathole_host.ipv4_address
}

Then all I need is to write the randomly generated domain to a file

resource "local_file" "domain_name" {
  # grab the domain from the previous resource
  content  = "${digitalocean_domain.rathole_domain.id}"
  # write it to a file
  filename = "${path.module}/rathole-domain.txt"
}

For this to work, all I needed to do is create a domain (dns zone) to my account, (do.mendy.dev) And if I create a new zone that is a subdomain of the do.mendy.dev, digitalocean (or tofu, I am not sure who does it), will automatically create the ns records in the do.mendy.dev zone, and delete the NS records when I run tofu destroy

Putting it all together

Now all that is left is to write some shell scripts to download and run the caddy/rathole binaries. This is done very simply with SSH

As well as a python script to create a new rathole config and Caddyfile, The script accomplishes 2 main goals

  1. Generate new token for each new session, this is done by getting some random bytes from the python secrets.token_urlsafe function
  2. Templates the config files with the token/domain name