Skip to content

Configuration

Configuration is resolved in this order (highest to lowest):

  1. CLI flags — explicit flags on the command line
  2. Environment variables — TS_* prefix
  3. YAML config file — specified with --config
  4. Managed profile credential — auth key only, when the profile references one
  5. Built-in defaults — hardcoded fallbacks

Browser exception (ADR-014): browser does not accept an environment-only credential. With browser --profile, --auth-key-file remains the highest precedence source; otherwise the managed profile credential is used and TS_AUTHKEY is ignored with an explicit warning.

Variable Description Example
TS_AUTHKEY Auth key for connect. Tailscale: tskey-auth-<id>-<secret>. Headscale: hskey-*. Plaintext in the environment and readable by child processes. On a shared or hardened host, connect --auth-key-file can leave it unset; init --auth-key-file avoids command-line exposure but writes TS_AUTHKEY to the generated .env (or the sibling .env in YAML mode). Browser mode intentionally ignores this variable; use browser --auth-key-file or a managed profile credential. tskey-auth-KEYID-SECRET
TS_TARGET Host address on the mesh network 100.82.151.104:3389
Variable Default Description
TS_LOCAL_ADDR 127.0.0.1:33389 Local bind address. Auto-derived in auto mode when unset.
TS_CONTROL_URL (Tailscale default) Custom control plane URL for self-hosted Headscale. Required for hskey- keys — they are rejected without it (a tskey- key routes to Tailscale SaaS by default). Must be an http(s):// URL.
TS_BOOTSTRAP_SSH (disabled) OpenSSH endpoint (user@host[:port]) used to reach a custom control plane through dynamic SOCKS forwarding. Requires TS_CONTROL_URL.
TS_BOOTSTRAP_SOCKS_ADDR 127.0.0.1:1055 Loopback-only SOCKS listener for SSH bootstrap. It is valid only when TS_BOOTSTRAP_SSH is set.
TS_HOSTNAME ts-bridge Node name in the admin console. Auto-generated per run in auto mode.
TS_STATE_DIR (per-user) Directory for node state (holds the private node identity). Default is a fixed per-user dir: Windows %LOCALAPPDATA%\ts-bridge\state, macOS ~/Library/Application Support/ts-bridge/state, Linux $XDG_STATE_HOME/ts-bridge/state (→ ~/.local/state/ts-bridge/state). Created with 0700 permissions. Ephemeral temp dir in auto mode with an instance. A relative override is warned about.
TS_AUTO_INSTANCE true Auto mode toggle. Set false to disable auto behavior.
TS_MANUAL_MODE false Force legacy persistent mode. Takes precedence over TS_AUTO_INSTANCE.
TS_INSTANCE_NAME (empty) Stable instance alias for deterministic local port selection.
TS_PORT_RANGE 33389-34388 Port range for auto mode (START-END).
TS_TIMEOUT 30s Timeout for tsnet initialization (control-plane handshake). Go duration format.
TS_DIAL_TIMEOUT 5s Per-connection target dial timeout. Keeps stuck dials from holding a slot across retries. Go duration format. (v1.8.0+)
TS_DRAIN_TIMEOUT 15s Timeout for graceful drain of active connections on shutdown. Go duration format.
TS_MAX_CONNECTIONS 1000 Maximum concurrent connections before rejecting new ones.
TS_IDLE_TIMEOUT (disabled) Close connections after this period of no traffic. Go duration format (e.g. 30m). Default 0 disables. (v1.6.0+)
TS_DIAL_RETRIES 3 Maximum retries for transient target dial failures. 0 disables. (v1.7.0+)
TS_DIAL_BACKOFF_BASE 1s Base backoff for dial retries (multiplied by 2^attempt, plus jitter). Go duration format. (v1.7.0+)
TS_DIAL_BACKOFF_MAX 30s Cap on backoff per retry attempt. Must be ≥ TS_DIAL_BACKOFF_BASE. (v1.7.0+)
TS_HEALTH_ADDR (disabled) Address for health/metrics HTTP server.
TS_VERBOSE false Enable debug logging. Also available as -v flag.
TS_LOG_FORMAT text Log output format (text or json).

The ts-bridge host commands resolve their settings as flags > environment (TS_HOST_*, including ./.env) > host file (--config) > per-OS defaults. ts-bridge host init --config <path> writes the host file; the variables below override it per run.

\ProgramData\ts-bridge\host.yaml
version: 1
host:
services:
- kind: rdp # rdp on Windows and Linux; ssh/vnc as their OS steps land
port: 3389
firewall: # Windows only
rule: Tailscale-RDP-Ingress
power: # Windows only
prevent_sleep: true
control_url: https://headscale.example.com # optional: the descriptor's control plane

Unknown keys warn and are ignored; an empty key, or an entry the OS has no step for, is an error before any step runs. host.control_url is read from the file only.

Variable Default Description
TS_HOST_RDP_PORT 3389 RDP listening port on the host.
TS_HOST_FIREWALL_RULE Tailscale-RDP-Ingress Name of the firewall rule.
TS_HOST_NO_SLEEP false Disable sleep mode on the host (Windows).

ts-bridge host serve reads the host.mesh block of the host file (--config); no environment variable sets it. Serve also ignores the TS_* variables and the YAML that tune connect: its dial, idle and drain timeouts, connection limit and bootstrap backoff are the built-in defaults. Unknown keys warn and are ignored; a key given with no or an empty value is an error.

Key Required Description
state_dir yes Absolute path holding the node identity. Windows: owned by SYSTEM or Administrators, no other SID in its DACL, no link or junction on the path. Linux: 0700, owned by root. Created protected when absent.
auth_key_file first start Absolute path to a tagged auth key, read only while state_dir holds no node. Must not be readable by other accounts.
hostname no Node name; defaults to the OS hostname in lower case.
bootstrap_ssh no user@host[:port] to reach host.control_url through OpenSSH, as connect --bootstrap-ssh. Needs host.control_url.
bootstrap_socks_addr no Loopback SOCKS address for that bootstrap; default 127.0.0.1:1055.
health_addr no Loopback address for /health/ready, which host check --json reads for published.

host.control_url is required by serve. Setup and recipes: the host serve runbook.

TS_VERBOSE, TS_AUTO_INSTANCE, TS_MANUAL_MODE accept: 1, true, yes, on (case-insensitive). All other values are treated as false.

YAML config is optional and supports non-sensitive settings only. The auth key must not be stored in YAML files.

Named profiles may store a non-secret managed credential reference:

profiles:
office:
target: "acemagic-office:45000"
credential: "office"

Store the key with ts-bridge auth set --profile office. The managed credential path and value are never exported through a tsb:// descriptor.

ts-bridge.yaml
target: "100.82.151.104:3389"
local_addr: "127.0.0.1:33389"
hostname: "office-laptop"
health_addr: "127.0.0.1:9090"
max_connections: 1000
idle_timeout: "30m"
log_format: "json"
# Optional: reach a blocked Headscale control plane through OpenSSH.
control_url: "https://vpn.example.com"
bootstrap_ssh: "deployer@bastion.example.com"
bootstrap_socks_addr: "127.0.0.1:1055"
Terminal window
# Load from YAML, auth key from env
ts-bridge connect --config ts-bridge.yaml
# Auth key from file (secure)
ts-bridge connect --config ts-bridge.yaml --auth-key-file /run/secrets/authkey
# Secure non-interactive initialization
ts-bridge init --auth-key-file /run/secrets/authkey --target 100.82.151.104:3389

For self-hosted Headscale, set TS_CONTROL_URL and use hskey-* auth keys:

Terminal window
TS_AUTHKEY=hskey-auth-xxxxx
TS_TARGET=100.64.0.5:3389
TS_CONTROL_URL=https://vpn.example.com

Keep that file at 600, or leave TS_AUTHKEY out of it entirely and start with ts-bridge connect --auth-key-file /run/secrets/authkey --control-url https://vpn.example.com.

The init wizard supports both Tailscale and Headscale — it detects the key prefix. For an hskey-* key it requires a control plane (--control-url, or --profile for a named profile) and refuses to write a configuration that connect would then reject; the pairing is enforced on write by the same validator the runtime applies on read.

Re-running init --format yaml in a directory that already holds a .env rewrites that file’s TS_TARGET and TS_CONTROL_URL from the flags it was given. That layer outranks the YAML file in the merge chain (flags > env > yaml > defaults), so a stale value there would otherwise be the one connect uses — which is why init writes the values it validated instead of preserving them, and validates a preserved control URL when no flag was given.

When the local network blocks or sinkholes the Headscale hostname, but SSH to a bastion remains available:

Terminal window
ts-bridge connect \
--control-url https://vpn.example.com \
--bootstrap-ssh deployer@bastion.example.com \
--auth-key-file /run/secrets/headscale-authkey \
--target mesh-host:3389

The system ssh binary opens a loopback dynamic SOCKS proxy. Only requests to the configured control-plane hostname are selected for that proxy; unrelated HTTP traffic keeps its existing proxy behavior. DNS resolution happens through the SSH server while the original URL hostname is retained for TLS SNI and certificate checks. The feature never edits the hosts file or disables certificate or SSH host-key verification.