10 minute read

How I access my self hosted services from anywhere

I thought external access would be the hardest part of self-hosting. Turns out, I had already solved it long ago.

Ever since I gave up on Spotify and started the process of migrating away from iCloud and other subscriptions, I knew I was heading straight into a classic infrastructure problem I could no longer ignore: external access.

I could happily live with a local-only Jellyfin setup. I mean, I don’t really need to stream my media while I’m away, and downloads from clients work just fine for that. But the moment I started seriously looking at a Google or Apple Photos alternative like Immich, it became obvious that external access wasn’t optional anymore. Otherwise, I would only be able to sync my photos when I got back home. And from previous experience, not having backups from trip photos can be disastrous.


This is just my setup and what works for me. I’m sure there are plenty of alternatives you could follow to arrive at the same solution. So take everything below with a generous pinch of salt.

The problem I had already solved

The funny thing is that the solution to access services externally was already sitting right there in my self-hosted stack.

A long time ago, when I installed Home Assistant, external access became necessary pretty quickly. Not because I wanted to turn lights on and off while away, but because location-based automations depend on it. The same goes for driving automations—when my phone connects to my car, things are supposed to happen at home. It only works if Home Assistant can talk back.

Back then, I explored a few options, and one stood out immediately, mostly because it’s free: Cloudflare Tunnels.

I set it up, got things working, and moved on. The goal was simple: make Home Assistant reachable from the outside.
Did I completely understand it? Not really.

Cloudflare Tunnels in plain terms

The way I think about Cloudflare Tunnels is as a private tunnel between two computers. One is your server at home, the other is the user accessing it. That’s obviously a simplification of what’s really happening, but it’s good enough to reason about what’s going on behind the scenes. It’s important to mention that these tunnels are one way only, and that’s outbound.

When you create a tunnel, Cloudflare gives you a URL that looks like this: https://{some-big-hash}.cfargotunnel.com

That big hash uniquely identifies your tunnel. On your server, you then run cloudflared, which tells Cloudflare where that tunnel should actually be pointed at.

Then, all you really need is a domain in front of it. Something like: https://some-service.yourdomain.com

No port forwarding needed. No static IP needed. No messing around with your router. Everything is encrypted from the user to your home.
And that’s exactly what I had running for Home Assistant already.

One tunnel, many services

Fast forward to today.

Besides Home Assistant, I now also have a bunch of other services running like Jellyfin, Immich, Syncthing, Vaultwarden, etc. I really didn’t like the idea of creating one tunnel per service. That felt wasteful and unnecessary.

So instead of pointing the tunnel at a specific service, I pointed it at a reverse proxy. More specifically, Nginx Proxy Manager. That’s yet another service I have running at home.

The Cloudflare Tunnel itself always points to the proxy manager, something like: http://10.0.0.254:9876

Inside Nginx Proxy Manager, I define which subdomain points to which local service. Roughly like this:

Plain Text
123
ha.domain.com        →  10.0.0.254:1234
jellyfin.domain.com  →  10.0.0.254:2345
immich.domain.com    →  10.0.0.254:3456

For me, all services are running on the same server. Hence why the local IP is always the same. Only the port changes.

The flow is pretty clear now: When I hit ha.domain.com, Cloudflare looks up the DNS records for my domain. The subdomain is configured to point to my cloudflared tunnel. That tunnel is configured to always point to my Nginx Proxy Manager (NPM), regardless of origin hostname. From there, NPM decides where the traffic should go based on the hostname.

NPM is basically acting as a bouncer at my server. It either points you at the right door, or bounces you off.

But that’s not all…

Maintenance, security and encryption

Once you start spinning up servers at home for your whole family to rely on, it becomes very obvious who the IT guy is: that’s you!

So not only do you need to make sure your family members can use the services you provide reliably, you also need to make sure that your and their data don’t get lost. You know, hard drives die.

That’s why I built my own backup system around it. Besides the actual server, I now have an on-site backup ready to use at anytime—which I have to update manually, by design—I also have an off-site backup I push regularly to. That gives me effectively 3 copies to make sure we don’t loose most of the existing data.

Then there’s encryption. You want to make sure your data is obfuscated from curious eyes. For me, this means any data leaving my house is encrypted. Which means my off-site backup is encrypted. And also means that any service accessible through the Cloudflare tunnel, also is encrypted—Cloudflare does this by default. Effectively, no data leaves the house unless I’m on an SSL connection.

Lastly, security. Besides guaranteeing no unauthorised person can read your data, you also want to make sure those people are stopped as far from it as possible.

For me, I use 3 rules:

  1. Only expose services through the Cloudflare Tunnel—it’s encrypted by default.
  2. Only expose services I actually need—a door that doesn’t exist, is one less door attackers can pry on.
  3. Use Cloudflare Access for emergencies—only I have access to.

Things like Immich, Home Assistant and Jellyfin are useful when your whole family can benefit from them. Those get exposed through the Cloudflare tunnel. But some things, like admin panels for Zigbee networks or even the router, need to stay as private as possible. Although you might need access to at some point while you’re away. That’s where rule #3 comes in.

The idea is simple: you need to expose your router admin panel to the outside world, but you only want you to have access to it. Enter Cloudflare Access.

Basically, you configure an Application in Cloudflare, and set a Policy that only accepts 1 email address: yours. Then apply this to a hostname like router.domain.com. Lastly, you create a host in NPM so that it knows where to point you.

Now, when you access router.domain.com you get asked for your email address (a page from Cloudflare) even before reaching your router admin panel. You get send a one-time code to your email, which when entered redirects you to the router admin panel.

A double-login process, if you will. Where only you—or the people you want—have access to.

You can even have this configured on Cloudflare and NPM, and then disable the host on NPM so that this bridge only exists when you need it and for the amount of time that you need it. In other words, when you’re away and need to connect to your router at home, you visit npm.domain.com, get past the Access page, log into NPM with your credentials, find the router.domain.com host and enable it. And then you can connect to your router.

What’s ahead for me

So far, I have successfully configured and ran around 10 self hosted services at home. The process has been straight forward for most of them. The hardest ones to get right for me were Opencloud and Immich—Opencloud being the biggest PITA by far.

I ended up not using Opencloud anyway because I figured a way to expose Synology Drive on a specific port and then pointed NPM to it, so that I have access to my files on the go without relying on Quickconnect.

What this means is that I know I can manage pretty much anything our digital life depends on and intend to slowly migrate to self hosted alternatives as time allows.

From my experience, you only need 3 subscriptions in your life:

With the knowledge I have today, everything else can be considered a commodity. So far, I have been running all these services on 6 year old NAS running an Intel Celeron 4-core processor. And the only reason why I’m building a new one is Frigate. This means an old computer can go a long way.

In the future, things like shared calendars, contacts, surveillance, document archive, shared documents and spreadsheets, or even instant messaging and papers are all going to be services I’ll want to self host. The less big companies have on my family, the better.

I’m already running Forgejo, so might as well migrate all my repos to it and self host all my websites too?
The sky really is the limit. Until you realise you could also run local LLMs. Then your GPU is the limit.

Stay tuned for the upcoming server build where I will set the groundwork for this new era of my life.

Takeaways

External access feels scary until you realise you don’t actually need to open your whole local network to the world. Cloudflare Tunnels remove a lot of that anxiety.

Pointing a tunnel at a reverse proxy instead of individual services simplifies everything. One entry point, many services. No port forwarding, no static IP, no fuss.

If I can run ten services on an old Celeron NAS, chances are you can too. Self-hosting is more accessible than most people assume.

Photo of Pedro