Big fan of Tailscale. It just works. The free tier is extremely capable.
One weakness is interactions between devices inside and outside the tailnet. Of course where possible you'll just want to add the device to your tailnet, but for some things like printers that's just not possible.
Tailscale offers subnet routing that's supposed to be for this, but it only works one direction, from tailnet devices to local devices, and it's extremely awkward. The devices in your local network do not get properly integrated into your tailnet, with their own IPs and all; instead the tailscale client on your tailnet device advertises a route to your local network. If you have multiple local networks, and they have overlapping IP ranges, you're fucked.
I've created two Home Assistant addons that solve two simple problems:
- HA Tailscale Inject: You have a device in your local network that can't run the tailscale client, like a printer, or an esp32, and want to access this device from your tailnet.
- HA Tailscale Reject: you have a device in your local network, and want to access a tailnet device from this local device, like a lokal Roku stick accessing a tailnet Jellyfin server
The repository is here, you can just reference it in the Home Assistant Addon settings.
As this handles extremely sensitive data, keys to your tailnet, I'd recommend to fork the repository to your own github account and reference the fork. Otherwise I could, if auto updates are enabled, steal your tailscale key. I won't, but you don't know that.
So how does it work?
HA Tailscale Inject
We connect to the Home Assistant docker socket and for each configured device that should be injected into the tailnet, create a container that registers itself with tailscale andforwards traffic to the local device. Internally this more or less is just a call to socat.
HA Tailscale Reject
We assume that Home Assistant itself is part of the tailnet and has a tailscale client running. We listen on a configured local port (on the homeassistant IP), then forward that traffic to the configured tailscale IP. It's just a simple NGINX configuration and technically doesn't interact with tailscale directly.
So how does it look in practice?
To make my local Denon AVR reachable from tailscale, I added an entry to Tailscale Inject with the following configuration:
To make my tailnet Jellyfin server reachable from my local network, I added an entry to Tailscale Reject with the following configuration:
With these, the receiver is reachable as denon-avr-x2600h.<my-tailscale-domain>.ts.net from anywhere, and local devices can access jellyfin under http://homeassistant.local:8096