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[]:
| Field | Type | Default | Allowed values | Description |
|---|---|---|---|---|
state_directory | string | tailscale | <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_key | string | (unset) | <auth key> | Tailscale (or Headscale) auth key for headless, non-interactive login. Required for an unattended node. |
control_url | string | controlplane.tailscale.com | <URL> | Coordination-server URL. Point it at a self-hosted Headscale to use an alternative control plane. |
ephemeral | bool | false | true | false | Register as an ephemeral node that the coordination server removes automatically once it goes offline. |
hostname | string | (OS hostname) | <string> | Node name to register on the tailnet. Defaults to the operating-system hostname. |
accept_routes | bool | false | true | false | Accept subnet routes advertised by other tailnet nodes. Opt-in. |
exit_node | string | (unset) | <node name or IP> | Route traffic through this tailnet node as an exit node. |
exit_node_allow_lan_access | bool | false | true | false | Permit 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_node | bool | false | true | false | Advertise this node as an exit node that other tailnet members can route through. |
advertise_tags | badoption.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_interface | bool | false | true | false | Use a real system TUN interface instead of the userspace gVisor netstack. Faster, but needs elevated privileges. |
system_interface_name | string | (auto) | <string> | Name for the system TUN interface when system_interface is enabled. |
system_interface_mtu | uint32 | (auto) | <bytes> | MTU for the system TUN interface when system_interface is enabled. |
udp_timeout | UDPTimeoutCompat | 5m | <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:
{
"endpoints": [
{
"type": "tailscale",
"tag": "ts-ep",
"auth_key": "tskey-auth-xxxxxxxxxxxx"
}
]
}Routing selected traffic through a tailnet exit node:
{
"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:
{
"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
tailscaletype is not registered. auth_keyenables headless (non-interactive) login — required for an unattended node. Without it the node cannot authenticate on its own.state_directorydefaults totailscaleand 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: trueregisters a node that the coordination server removes automatically once it goes offline — handy for short-lived or containerized instances.accept_routesis opt-in: subnet routes advertised by other nodes are ignored unless you enable it. Conversely,advertise_routes/advertise_exit_nodeturn 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: truebinds a real TUN device for higher throughput at the cost of elevated privileges.- Tailscale's MagicDNS can be consumed by adding a
tailscaleDNS server to thednsblock, which resolves*.ts.netnames through the node.
Cross-core notes
- mihomo exposes Tailscale as an outbound (
proxies[]withtype: 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)
