SirTunnel, a personal ngrok alternative

Happy New Year!

From time to time I need to expose a development web site or web service to the world. In the past, I’ve used ngrok for that, and of course long ago I built ReverseHTTP which is somewhere in the same ballpark, but I recently got fed up with the state of affairs and decided to see whether there was something simple I could run myself to do the job.

I found Anders Pitman’s SirTunnel:

Minimal, self-hosted, 0-config alternative to ngrok. Caddy+OpenSSH+50 lines of Python.

It really is desperately simple. A beautiful bit of engineering. At its heart, it scripts Caddy’s API to add and remove tunnels on the fly. When you SSH into your server, you invoke the script, and for the duration of the SSH connection, a subdomain of your server’s domain forwards traffic across the SSH link.

I’ve forked the code for myself. So far, I haven’t changed much: the script cleans up stale registrations at startup, as well as at exit, in case a previous connection was interrupted somehow; and I’ve added support for forwarding to local TLS services, with optional “insecure-mode” for avoiding certificate identity checks.

To get it running on a VM in the cloud, install Caddy (there’s a caddy package for Debian bookworm and sid), then disable the systemd caddy service and enable the caddy-api service:

apt install caddy
systemctl disable caddy
systemctl enable caddy-api
systemctl stop caddy
systemctl start caddy-api

Set up a wildcard DNS record for your server - something like *.demo.example.com. Each tunnel will be made available on a subdomain of demo.example.com.

Then use the API to upload a simple “global” config. Here’s mine:

{
  "apps": {
    "http": {
      "servers": {
        "default": {
          "logs": {},
          "listen": [":443"],
          "routes": []
        }
      }
    }
  }
}

Upload it by putting it in a file caddy_global.json and run

curl -L localhost:2019/load -H 'Content-Type: application/json' -d @caddy_global.json

Then, make sure SirTunnel’s sirtunnel.py script is available somewhere on the server to your SSH user account.

At that point, to expose a local development service running on port 8443 to the world:

ssh -t -R 8443:localhost:8443 YOURSERVER path/to/sirtunnel.py YOURAPP.demo.example.com 8443

I wrapped that up in a tiny script so that I didn’t have to remember the details of that incantation, but it’s simple enough that you could easily just type it in the terminal each time.

Many thanks to Anders Pitman for a really nice piece of software!