DIY Tailscale Funnel


I really do love Tailscale, if it wasn't obvious by my previous infrastructure update post. When they announced their Funnel feature, I jumped on it as an opportunity to try an alternative to Cloudflare Tunnels. And while it worked pretty well, I was mildly disappointed by some missing features. Key among them being the ability to setup custom DNS entries to point to services. So not only was the URL really long, I also didn't fully have control over it and I also had to do path routing to various services, which can be finicky at the best of times. But I put up with it for a while, because I didn't care too much. At least, that's what I kept telling myself.

Eventually, though, it did start to bug me. So I went looking for a solution. Continuing to avoid Cloudflare to explore smaller alternatives, I landed on a solution I'd tossed around for a while and Hack13 confirmed was a viable approach - a dedicated "ingress" server that acted as the public endpoint to expose services without exposing my actual home network. The server can communicate with services hosted in my home through Tailscale, with nginx as the reverse proxy (Hack13 uses Caddy for this, but the nginx muscle memory is very, very strong). Armed with this information, I went about exposing my Forgejo instance.

The actual configuration to make this happen is really straightforward, and can be seen in my infra repo. The one downside of this approach is because the DNS points to another server, I can't easily use SSH for git operations. However, Tailscale has a trick up its sleeve! It can specify split DNS configurations, meaning that I can tell Tailscale clients to check a custom DNS server for specific domains and subdomains. For the time being, I've set this up to query a CoreDNS server running on my NAS over Tailscale, although I (hope) this is temporary. I'm still trying to figure out the best way to setup a custom DNS server that is both lightweight (it just needs to serve a few custom records) and preferably distributed in some way for redundancy. And also doesn't require me to ship a plaintext file of records like I have to do now.

I digress. With this split DNS, I get two big perks - one, because the DNS points directly to the server through Tailscale, I can use git over SSH as long as I'm connected. Second, because Tailscale can do direct connections, requests get routed directly to the server when I'm at home! So there's far less latency and is entirely transparent. Some rough measurements, when routing directly it takes about 182ms to load the homepage when not logged in, and 622ms to load when routing through the ingress server.

Said ingress server is a small Oracle Cloud free tier ARM server (I say small, it's 2 vCPU cores and 4GB of RAM), and while I'm always cautious of Oracle's cloud offering, at least it's defined as a Nix configuration file so moving it and swapping DNS records isn't too tedious should something happen. This server is located in the same country as me, so latency between it and my home server is fairly low, although those in other countries or further from the datacenter it's located in will probably have higher latency. It's an unfortunately unavoidable downside that I could probably work around with some other points of pressence, but frankly this setup doesn't need it.

Down the line I'll probably expand the exposed services, the first one on my list being my personal n8n instance for automation. I might also consider opening up my FreshRSS instance for public use, if there is any interest. The Forgejo instance currently closed to registrations, but I've enabled GitHub and Codeberg authentication and might allow registration through those so others can more easily submit pull requests to my various projects (the plan is to eventually mirror some back to GitHub, but I'm holding out hope that ForgeFed comes to fruition).

If you have any questions about this setup, feel free to reach out either via the FediVerse or Matrix!