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.
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.

Source: option/tailscale.go:13-32 · pinned at v1.13.11 (553cfa1)

The struct also embeds DialerOptions for the underlying socket — bind_interface, routing_mark, detour, etc.

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.

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:13-32 · v1.13.11 (553cfa1)

Core Tutorial by Argsment