opnsense / opnsense/plugins

[os-tailscale] Support multiple concurrent Tailscale/Headscale instances

Open
#5,411 0 comments 2 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
PHP
Stars
1.2k
Forks
863
Avg merge
2d 6h
Merged PRs (30d)
10

Description

Important notices
Before you add a new report, we ask you kindly to acknowledge the following:

Is your feature request related to a problem? Please describe.

The os-tailscale plugin (currently version 1.2) manages exactly one tailscaled daemon, one state directory, and one tailscale0 interface. This makes it impossible, via the plugin UI, to join OPNsense to more than one Tailscale/Headscale control plane simultaneously.

This is a significant functional gap relative to the other VPN plugins that ship with OPNsense:

  • WireGuard (os-wireguard): supports multiple instances natively via GUI
  • OpenVPN (core): supports multiple clients/servers natively via GUI
  • IPsec (core): supports multiple connections natively

Real-world use cases that hit this wall:

  • Consultants / MSPs who need to reach multiple client environments, each with their own Tailscale or self-hosted Headscale control server.
  • Users separating work and personal tailnets where merging under one control server is not possible because one side (employer, client) is administered externally.
  • Multi-tenant routing where OPNsense should act as the gateway/router for LAN clients into several independent tailnets at once — exactly what is already possible today with multiple WireGuard peers or OpenVPN clients.

The only current workaround is to SSH into OPNsense and hand-write additional rc.d scripts (tailscaled_work, tailscaled_client, etc.) with custom -state, -socket, -tun, and -port flags, then manually register each instance via CLI (tailscale --socket=... up --login-server=... --auth-key=...) and manually assign the resulting tailscale1, tailscale2 tun devices as OPNsense interfaces. This has significant drawbacks:

  • No UI, no status display, no log integration for instances beyond the first
  • Not idempotent across plugin updates
  • Invisible to OPNsense backup/restore
  • Each interface assignment, per-instance firewall rule set, and outbound NAT rule must be created and maintained manually
  • Fragile across OPNsense major upgrades and os-tailscale plugin updates
  • Not discoverable — most users will simply conclude OPNsense cannot do what other VPN plugins already do

Describe the solution you'd like

Extend os-tailscale so that the data model supports N named instances rather than a single global configuration, modeled after how os-wireguard represents multiple peer/server configurations.

For each instance the plugin should manage:

  • Identity: user-provided name (e.g. personal, work, client-acme)
  • Daemon parameters:
    • Unique --state directory (e.g. /var/db/tailscale-<name>/)
    • Unique --socket path (e.g. /var/run/tailscale-<name>.sock)
    • Unique --tun device name (e.g. tailscale0, tailscale1, ...)
    • Unique --port (starting from 41641 and incrementing, or user-configurable)
  • Control plane: --login-server URL (or default to hosted Tailscale), per-instance auth key
  • Runtime preferences: accept-routes, advertise-routes, exit-node flags, tags, hostname — all existing plugin settings, scoped per instance
  • Service management: one rc.d script per instance, started/stopped independently by configd
  • Interface integration: each tailscale<N> tun device assignable as a distinct OPNsense interface with its own firewall rules, outbound NAT, and status page

Proposed UI layout (mirrors os-wireguard):

VPN → Tailscale
├── Overview                  ← list of configured instances with status
├── Instances
│   ├── [+ Add instance]
│   ├── personal              ← click to edit settings / authentication / advanced
│   ├── work
│   └── client-acme
└── Diagnostics / Log File    ← per-instance selector

Implementation notes:

The underlying daemon already supports this. tailscaled accepts -state, -socket, -tun, and -port flags precisely to enable multiple instances on the same host (see upstream workaround in tailscale/tailscale#5721). The tailscale CLI honours --socket=<path> for targeting a specific daemon. The core work is therefore in the plugin:

  1. Convert the singleton data model (XML schema) to a keyed list of instances
  2. Generate one rc.d script per instance from a single template
  3. Wire each instance into configd for start/stop/status/reload
  4. Expose each tailscale<N> tun device to the interface assignments dropdown
  5. Update the tailscale CLI wrapper calls in the plugin to always pass --socket=... explicitly
  6. Migration path: existing single-instance installs become a single default instance with no behavior change

Describe alternatives you've considered

  • Sidecar VM or container (e.g. Alpine Linux VM beside OPNsense, running one tailscaled per tailnet and acting as subnet router back into a dedicated OPNsense transit VLAN). Works, but requires a hypervisor, extra machine management, and is harder to integrate into OPNsense's unified firewall/interface model than a native plugin instance.

  • FreeBSD jails on the OPNsense host. Better isolation but not a first-class OPNsense feature, and user-hostile compared to a plugin UI.

  • Manual rc.d scripts as described above. Works but defeats the purpose of having a plugin at all for instances beyond the first.

  • Community fork of os-tailscale adding this capability as a separate plugin. Feasible but fragments the ecosystem and creates ongoing duplicate-maintenance burden — upstream support would be the right home.

  • Single Headscale with users/tags/ACLs to logically separate environments. Only viable when the user controls all environments, which is not the case for client/employer tailnets.

Additional context

Existing demand:

Precedent within OPNsense: os-wireguard, core OpenVPN, and core IPsec all support multiple instances. There is no principled reason for Tailscale to be the only VPN technology in OPNsense limited to a single control plane per firewall.

Related existing issues in this repository that would benefit from the refactor:

Secondary but related gap (optional / follow-up):

[Tailscale KB 1299](https://tailscale.com/kb/1299/opnsense-unbound) acknowledges that tailscaled DNS settings are not integrated with OPNsense's Unbound resolver, forcing manual Domain Override configuration per tailnet. A multi-instance plugin would be a natural place to also surface per-instance DNS integration (auto-writing Unbound Domain Overrides for each instance's base domain pointing at 100.100.100.100 or the instance's configured DNS). Out of scope for this FR but worth anticipating in the design.

Environment of the requester:

  • OPNsense: 25.x
  • os-tailscale: 1.2
  • Use case: single OPNsense firewall needing to terminate three independent tailnets concurrently (personal Headscale, client development Headscale, work Tailscale), with LAN clients routed through the appropriate tunnel per-destination via outbound NAT on each tailscale<N> interface.

Disclosure:
Assisted by Antropic Claude Opus 4.7

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reading the existing os-tailscale singleton XML schema and its rc.d/configd service entry points, then compare them with os-wireguard's multi-instance model. Trace how tailscale CLI calls, tun devices, and interface assignments are represented today. Done means named instances can be managed independently with migration from the existing single instance and per-instance interface integration.

Written by the indexing model from the issue text.

Assessment

Tech stack
php
Domain
networking
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.