Skip to content

Tailscale — sing-box ​

sing-box can join a Tailscale tailnet and expose it as a routable endpoint. The node runs on a userspace gVisor network stack by default, so no system Tailscale daemon is required; set system_interface: true to use a real TUN device instead. Point control_url at a self-hosted Headscale to use an alternative coordination server.

Endpoint, not outbound

Tailscale is configured as an endpoint in sing-box, not an outbound. The block sits under the root endpoints[] array with type: "tailscale", and is referenced from routing rules just like any named outbound.

Endpoint options ​

type: "tailscale" under endpoints[]:

FieldTypeDefaultAllowed valuesDescription
state_directorystringtailscale<directory path>Directory where the node persists its state and keys, resolved relative to the working directory. Reuse it across restarts to keep the same tailnet identity.
auth_keystring(unset)<auth key>Tailscale (or Headscale) auth key for headless, non-interactive login. Required for an unattended node.
control_urlstringcontrolplane.tailscale.com<URL>Coordination-server URL. Point it at a self-hosted Headscale to use an alternative control plane.
ephemeralboolfalsetrue | falseRegister as an ephemeral node that the coordination server removes automatically once it goes offline.
hostnamestring(OS hostname)<string>Node name to register on the tailnet. Defaults to the operating-system hostname.
accept_routesboolfalsetrue | falseAccept subnet routes advertised by other tailnet nodes. Opt-in.
exit_nodestring(unset)<node name or IP>Route traffic through this tailnet node as an exit node.
exit_node_allow_lan_accessboolfalsetrue | falsePermit direct LAN access while an exit node is in use.
advertise_routes[]netip.Prefix[][<CIDR>]Subnet routes to advertise to the tailnet, turning this node into a subnet router.
advertise_exit_nodeboolfalsetrue | falseAdvertise this node as an exit node that other tailnet members can route through.
advertise_tagsbadoption.Listable[string](unset)[<tag>]ACL tags to advertise for this node (e.g. tag:server), used by tailnet access policies.
listen_portuint16(auto)<port>UDP port for WireGuard and peer-to-peer traffic. Picked automatically by default.
relay_server_port*uint16(unset)<port>Run the built-in peer-relay server on this UDP port to help other nodes traverse NAT.
relay_server_static_endpoints[]netip.AddrPort(unset)[<ip:port>]Statically advertised ip:port endpoints for the relay server when its public address cannot be discovered automatically.
system_interfaceboolfalsetrue | falseUse a real system TUN interface instead of the userspace gVisor netstack. Faster, but needs elevated privileges.
system_interface_namestring(auto)<string>Name for the system TUN interface when system_interface is enabled.
system_interface_mtuuint32(auto)<bytes>MTU for the system TUN interface when system_interface is enabled.
udp_timeoutUDPTimeoutCompat5m<duration>Idle timeout for UDP sessions handled by the endpoint.
ssh_server*TailscaleSSHServerOptions(disabled)true | TailscaleSSHServerOptionsRun a Tailscale SSH server on tailnet port 22. true equals { "enabled": true }; the object form adds disable_pty, disable_sftp and disable_forwarding. Access is governed by the SSH ACL in the Tailscale admin console; switching to another local user needs root on Linux / macOS.
taildrop_directorystringTaildrop<directory path>Where files received via Taildrop are stored. Relative paths resolve against the working directory, like state_directory.

Source: option/tailscale.go:15-37 · pinned at v1.14.2 (af6e64c)

The struct also embeds DialerOptions (bind_interface, routing_mark, detour, …), but they only control how the node connects to the control plane — not the traffic carried over the tailnet.

Examples ​

Minimal endpoint joining a tailnet with a headless auth key:

json
{
  "endpoints": [
    {
      "type": "tailscale",
      "tag": "ts-ep",
      "auth_key": "tskey-auth-xxxxxxxxxxxx"
    }
  ]
}

Routing selected traffic through a tailnet exit node:

json
{
  "endpoints": [
    {
      "type": "tailscale",
      "tag": "ts-ep",
      "auth_key": "tskey-auth-xxxxxxxxxxxx",
      "hostname": "sing-box-node",
      "exit_node": "us-exit-1",
      "exit_node_allow_lan_access": true,
      "accept_routes": true
    }
  ],
  "route": {
    "rules": [
      { "domain_suffix": [".example.com"], "outbound": "ts-ep" }
    ]
  }
}

Against a self-hosted Headscale coordination server, with a dedicated state directory:

json
{
  "endpoints": [
    {
      "type": "tailscale",
      "tag": "ts-headscale",
      "auth_key": "<headscale pre-auth key>",
      "control_url": "https://headscale.example.com",
      "state_directory": "tailscale-state"
    }
  ]
}

Notes ​

  • The Tailscale endpoint is only compiled into gVisor-enabled builds. On a build without gVisor the tailscale type is not registered.
  • auth_key enables headless (non-interactive) login — required for an unattended node. Without it the node cannot authenticate on its own.
  • state_directory defaults to tailscale and is resolved relative to the working directory. The node persists its keys and machine identity there, so reusing the same directory keeps the same tailnet identity across restarts.
  • ephemeral: true registers a node that the coordination server removes automatically once it goes offline — handy for short-lived or containerized instances.
  • accept_routes is opt-in: subnet routes advertised by other nodes are ignored unless you enable it. Conversely, advertise_routes / advertise_exit_node turn this node into a subnet router / exit node for the rest of the tailnet.
  • system_interface: false (the default) runs entirely in userspace via gVisor — unprivileged but slower. system_interface: true binds a real TUN device for higher throughput at the cost of elevated privileges.
  • Tailscale's MagicDNS can be consumed by adding a tailscale DNS server to the dns block, which resolves *.ts.net names through the node. With accept_search_domain: true, single-label queries such as my-device are retried against each Tailscale search domain.
  • A tailscale certificate provider (top-level certificate_providers, with endpoint set to this endpoint's tag) can issue TLS certificates for the node's tailnet (ts.net) name; MagicDNS and HTTPS must be enabled in the admin console.

Cross-core notes ​

  • mihomo exposes Tailscale as an outbound (proxies[] with type: tailscale) using hyphenated field names rather than an endpoint. See Tailscale — mihomo.
  • Xray-core has no Tailscale support.

Source: option/tailscale.go:15-37 · v1.14.2 (af6e64c)

Core Tutorial by Argsment