Homelab networking: Tailscale meets VergeOS for reachability without routing
The locked room
I had an admin tier I was happy about: a VergeOS internal network for the management VMs — the jump host, the box with vrg installed, the handful of internal tooling boxes you don't want anywhere near the public network. Outbound internet for package updates, but the upstream had no route back in. Exactly the isolation I wanted. Until my laptop needed to talk to it.
The list of standard answers all failed the smell test. Open the upstream router to advertise the subnet via OSPF or BGP — works, but now I'm asking the upstream device for cooperation, and now anything on the wider network that was already trusted can hit my admin VMs. That's a trust-boundary expansion I didn't ask for, on the network where I want it least. Port-forward SSH on the cluster's external IP — public exposure of the admin plane, no thanks. Tunnel through a single shared jump host every time — fine for one shell, miserable for vrg, the patching workflow, the on-call who just wants to reach a console. Every tool that needs to reach an admin VM now needs its own tunnel story.
What I actually wanted was: my laptop, on the tailnet I already had, reaches 192.168.0.0/24 like it was on the next desk over. No BGP. No public exposure. Same identity controls I already trust everywhere else.
A framing note before the mechanics: this is a homelab-shaped post. My tailnet has one human on it, and the pattern serves that audience. It scales the same way to a tailnet of fifty — invite people through Tailscale's admin UI and optionally scope their reach with ACLs so the dev team can only reach the dev subnet, the on-call group can only reach the monitoring host, and so on. If you ever need the opposite direction — exposing a service on your tailnet to someone outside it entirely — that's what Tailscale Funnel is for: it publishes a specific tailnet service to the public internet without giving the recipient tailnet membership. Different feature, different problem. The rest of this post stays on the inward case: tailnet members reaching VergeOS-internal subnets.
A subnet router is just a VM
The pattern, once I saw it, is small. Tailscale has a feature called subnet routing — a node on the tailnet can advertise an arbitrary CIDR, and other tailnet nodes (with --accept-routes) will route traffic for that CIDR through it. It doesn't matter what the node is or where it lives. It just has to be on both networks: the tailnet, and the subnet it's advertising.
So: deploy a small Linux VM on the isolated VergeOS network — or, if you're like me, repurpose the admin jump host that's already there. Mine is called adminjump and it runs vrg for everything else; advertising the subnet is just one more job for the same VM. One NIC on the isolated network is the only requirement.
Install Tailscale. On Ubuntu/Debian the official one-liner handles the repo, signing key, and package install in one step:
curl -fsSL https://tailscale.com/install.sh | sh
Then bring it up advertising the subnet — this is the same command that triggers the tailnet auth flow, so Tailscale will print a URL; open it in a browser, sign in to your tailnet, and approve the machine:
sudo tailscale up \
--advertise-routes=192.168.0.0/24 \
--accept-routes \
--accept-dns=false \
--hostname=adminjump
Three flags worth pausing on. --advertise-routes is what makes this work — without it, the VM is just another tailnet node. --accept-routes is here because in this homelab adminjump is also a consumer of tailnet routes (a separate subnet router elsewhere advertises the management network onto the same tailnet); since tailscale up re-applies the entire flag set, leaving --accept-routes out would silently drop that consumer side. Include it whenever the router VM also needs to reach tailnet-routed subnets itself; on a strictly producer-only VM, you can omit it. --accept-dns=false keeps the router from inheriting the tailnet's DNS, which matters because this VM lives in two trust domains and you almost never want it resolving names from the side it's bridging to.
Then approve the route in the Tailscale admin console — Machines → adminjump → Edit route settings → enable the advertised subnet. (This step is easy to forget. The VM will come up green in the admin without it, and routes won't flow. Approval is a separate, deliberate gate.) Finally, on any other tailnet device that needs to reach the subnet:
sudo tailscale set --accept-routes
set --accept-routes is the right verb here, not tailscale up --accept-routes — up re-applies the entire flag set and will quietly drop other config you've layered on. set is non-destructive. Learned that one the hard way.
That's the build. My admin network has one inhabitant today — adminjump itself — and from my laptop ssh [email protected] reaches it as if I were at a desk on the network. (Adminjump is also reachable directly via its tailnet IP, since it's a tailnet node in its own right; the subnet path and the direct path both work.) The pattern's real payoff lands the moment I add a second admin VM — a config-management box, a backup jump host, whatever — to 192.168.0.0/24: it'll be reachable from my laptop the same way, through the routed subnet, with no extra setup. WireGuard handles the encryption, the tailnet handles the identity, and the upstream network sees nothing — every packet between my laptop and an admin VM lands in adminjump as a tailnet flow and exits as a local packet on the isolated subnet. Nothing leaks past the choke point.
The mental model that helped: the subnet router VM is an edge port on the tailnet, and the isolated VergeOS network is hanging off it. From the tailnet's perspective, that subnet just exists.
What this gets you that OSPF/BGP doesn't
A handful of things, and they compound.
Trust is identity-based, not topological. The tailnet ACL controls who can use which routes. I can say "the platform group can reach 192.168.0.0/24, but the support group can only reach the patch-management host" without touching the upstream router or the isolated network's config. Compare to BGP, where reachability is "did the route get advertised, and does the receiver accept it" — a mostly binary, mostly topological question that doesn't know who you are.
You don't need anyone's cooperation. No firewall change request, no upstream router config push, no certificate authority involvement. The subnet router VM plus a route approval in the Tailscale admin is the entire change surface. Adding a second isolated network is the same recipe: deploy another small VM, advertise its subnet, approve. The two isolated networks don't know about each other, which is exactly the point.
Nothing is publicly exposed. The router VM has zero ingress from the wider internet — it has one NIC on a private subnet that wasn't reachable to begin with, and it dials out to the Tailscale coordination server. Compare to port forwarding or a public jump host, both of which need a publicly routable IP and a listener that anyone on the internet can attempt to reach.
It's encrypted by default and you didn't have to build the encryption. WireGuard is the data plane, Tailscale is the control plane, and you didn't write either. The closest classical equivalent — an IPSec tunnel between the cluster and the office — has roughly two orders of magnitude more operational tax for less functionality.
Failure modes are visible. The router VM is a Linux box you can ssh into. tailscale status is one command. If routing breaks, you can tell which side broke. Compare to debugging an OSPF adjacency that flapped because the upstream switch's hello timer changed during a maintenance window nobody told you about.
The shape of the argument: BGP and OSPF are routing protocols built for networks that share trust transitively. "I want my team to reach a specific isolated subnet" is not that problem. The right tool is an overlay that does identity-aware reachability, and the subnet router pattern lets you bolt that overlay onto any VergeOS internal network the moment a small VM can sit on it.
The fine print
A few things to know before you reach for this.
The subnet router is a privileged choke point. Anything on the tailnet that the ACL grants subnet access is now one packet away from anything on the subnet. If you wouldn't put a given user on the L3 LAN, don't grant them the route. Use Tailscale ACLs to scope; don't rely on the subnet router itself to enforce.
Mind your CIDR. 192.168.0.0/24 and 192.168.1.0/24 are extremely common home and SOHO LAN ranges — including, awkwardly, the one this post advertises. If a tailnet client's local network is already on the same CIDR you're advertising, the routes collide in the OS routing table and behavior gets unpredictable: sometimes the local LAN wins, sometimes the tailnet route wins, sometimes one breaks when the other reconnects. For any isolated VergeOS network you plan to advertise, pick a less-trafficked RFC1918 slot; the upper end of 10.0.0.0/8 and most of 172.16.0.0/12 are usually clear.
Patch the router VM. It's a small VM with little surface area, but it bridges two trust domains. Treat it like infrastructure, not like a side project. Subscribe to Tailscale's update channel, run unattended-upgrades, and snapshot before changes — VergeOS makes snapshotting nearly free, so use it.
MagicDNS doesn't extend onto the subnet. Tailscale gives every tailnet node a name; it does not give names to bare IPs sitting behind a subnet router. If you want tools01.admin.internal to resolve from your laptop, you need DNS on the isolated network and a custom split-DNS entry in the tailnet admin pointing the right zone at the right resolver. It's not hard, but it's not automatic, and it's the most common "why doesn't this work?" follow-up.
Latency adds one hop. Traffic from your laptop traverses the WireGuard tunnel to the router VM, then a local hop onto the isolated subnet. For most workloads this is invisible. For chatty SQL or NFS, measure before committing.
One router per network, by default. You can advertise the same CIDR from two routers for HA — Tailscale will pick one and fail over. The failover is much faster than BGP convergence but it's not instant. Test the behavior before you stake uptime on it.
What this all adds up to
If you're running VergeOS — or any infrastructure that lets you drop VMs onto isolated networks — and you have users (or yourself) who need access to those networks from elsewhere, the subnet router pattern is worth its very small weight in setup time.
Stop expanding the trust boundary to solve a reachability problem. OSPF, BGP, and "make it routable" all answer the wrong question. The question isn't "should this subnet exist on the wider network?" It's "should this user be able to reach this subnet?" Identity-based overlays answer the second one cleanly; routing protocols answer the first one and pretend it was the second.
A tiny VM is a feature, not a workaround. The asymmetry is the nice part: a 1-vCPU, 512 MB Linux VM is enough to expose an entire subnet to a tailnet of any size. The cost of adding another isolated network is one more such VM.
Match the tool to the trust model. Routing protocols share routes. Tailscale shares identity-scoped reachability. For "let my team reach internal stuff," the second is almost always what you actually wanted.
If you're already a VergeOS user and a Tailscale user, the integration is genuinely just: deploy the VM, install one package, advertise the subnet, approve the route. The rest is the network you already had.
One honest postscript: this post compares Tailscale to OSPF/BGP on architectural grounds, but the pragmatic reason I run Tailscale here isn't just architectural — it's also that I haven't built the OSPF peering between my UniFi router and VergeOS yet. Tailscale is what works in my homelab today. A future post will tackle the OSPF case directly, on its own merits, and revisit the comparison from the other side.