Skip to content

Getting Started

import { Tabs, TabItem } from ‘@astrojs/starlight/components’;

Grab the latest release from GitHub Releases. Builds are available for six platform/architecture combinations.

Terminal window
# Download
curl -LO https://github.com/mlorentedev/ts-bridge/releases/latest/download/ts-bridge-linux-amd64.tar.gz
# Extract
tar -xzf ts-bridge-linux-amd64.tar.gz
cd ts-bridge-linux-amd64
# (Optional) Install to PATH
sudo mv ts-bridge /usr/local/bin/
Terminal window
# Download
curl -LO https://github.com/mlorentedev/ts-bridge/releases/latest/download/ts-bridge-linux-arm64.tar.gz
# Extract
tar -xzf ts-bridge-linux-arm64.tar.gz
cd ts-bridge-linux-arm64
# (Optional) Install to PATH
sudo mv ts-bridge /usr/local/bin/
Terminal window
# Download and extract manually from GitHub Releases
# OR use PowerShell:
Invoke-WebRequest -Uri "https://github.com/mlorentedev/ts-bridge/releases/latest/download/ts-bridge-windows-amd64.zip" -OutFile "ts-bridge.zip"
Expand-Archive -Path "ts-bridge.zip" -DestinationPath "ts-bridge"
cd ts-bridge
# (Optional) Add to PATH
$env:Path += ";$pwd"
Terminal window
# Download
curl -LO https://github.com/mlorentedev/ts-bridge/releases/latest/download/ts-bridge-darwin-amd64.tar.gz
# Extract
tar -xzf ts-bridge-darwin-amd64.tar.gz
cd ts-bridge-darwin-amd64
# (Optional) Install to PATH
sudo mv ts-bridge /usr/local/bin/
Terminal window
# Download
curl -LO https://github.com/mlorentedev/ts-bridge/releases/latest/download/ts-bridge-darwin-arm64.tar.gz
# Extract
tar -xzf ts-bridge-darwin-arm64.tar.gz
cd ts-bridge-darwin-arm64
# (Optional) Install to PATH
sudo mv ts-bridge /usr/local/bin/

Copy .env.example to .env and set the two required variables:

Terminal window
cp .env.example .env
Terminal window
copy .env.example .env

Edit the .env file with your auth key and target:

.env
TS_AUTHKEY=tskey-auth-kXXXXXXXXX # From Tailscale admin console
TS_TARGET=100.82.151.104:3389 # Host Tailscale IP + port

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

Terminal window
# .env (Headscale)
TS_AUTHKEY=hskey-auth-xxxxx
TS_TARGET=100.64.0.5:3389
TS_CONTROL_URL=https://vpn.example.com
Variable Description Example
TS_AUTHKEY Auth key. Tailscale: generate here. Headscale: headscale preauthkeys create. Prefix: tskey- or hskey-. tskey-auth-kXXXXXX
TS_TARGET Host address on the mesh network. Supports IP or MagicDNS hostname. 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_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 (e.g. ~/.local/state/ts-bridge/state on Linux, %LOCALAPPDATA%\ts-bridge\state on Windows). 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, distinct from TS_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 in either direction. Go duration format (e.g. 30m). Default 0 disables. Useful for reclaiming slots from abandoned RDP sessions. (v1.6.0+)
TS_DIAL_RETRIES 3 Maximum retries for transient target dial failures. 0 disables retry. (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).
Terminal window
# Run with .env config
./ts-bridge connect
# Run with verbose logging
./ts-bridge connect -v
# Run with all flags inline (overrides .env)
./ts-bridge connect --target 100.82.151.104:3389 --auth-key tskey-auth-kXXXXXXXXX
# Interactive setup wizard
./ts-bridge init
Terminal window
# Run with .env config
.\ts-bridge.exe connect
# Run with verbose logging
.\ts-bridge.exe connect -v
# Run with all flags inline (overrides .env)
.\ts-bridge.exe connect --target 100.82.151.104:3389 --auth-key tskey-auth-kXXXXXXXXX
# Interactive setup wizard
.\ts-bridge.exe init
  1. CLI flags (highest)
  2. Environment variables (TS_*)
  3. YAML config file (--config)
  4. Built-in defaults (lowest)

Once ts-bridge is running, it prints the local port. Connect your RDP client:

Terminal window
# FreeRDP
xfreerdp /v:127.0.0.1:<LOCAL_PORT> /u:Username /cert:ignore
# Remmina
remmina --new-protocol RDP --server 127.0.0.1:<LOCAL_PORT>
# SSH
ssh -p <LOCAL_PORT> user@127.0.0.1
Terminal window
:: Built-in Remote Desktop Connection
mstsc /v:127.0.0.1:<LOCAL_PORT>
:: PowerShell
Start-Process mstsc -ArgumentList "/v:127.0.0.1:<LOCAL_PORT>"
Terminal window
# Microsoft Remote Desktop
# Open app → Add PC → 127.0.0.1:<LOCAL_PORT>
# SSH
ssh -p <LOCAL_PORT> user@127.0.0.1

For SSH targets, use any SSH client on any platform:

Terminal window
ssh -p <LOCAL_PORT> user@127.0.0.1

Set TS_HEALTH_ADDR to enable the HTTP health and metrics server:

Terminal window
TS_HEALTH_ADDR=127.0.0.1:9090
Terminal window
curl http://127.0.0.1:9090/health/live # {"status":"ok"} -- process alive
curl http://127.0.0.1:9090/health/ready # {"status":"ok"} -- tsnet tunnel up
curl http://127.0.0.1:9090/metrics # Connection stats (JSON)

Or use the status subcommand for a human-readable summary:

Terminal window
# One-shot summary
ts-bridge status
# Continuous watch
ts-bridge status --watch --interval 2s
# Raw JSON metrics
ts-bridge status --json

The machine you connect to (the host) needs Tailscale installed natively with admin rights.

  • Windows Pro, Enterprise, Education, or Server (Home edition cannot host RDP)
  • Remote Desktop enabled in Settings → System → Remote Desktop
  • Administrator privileges
Terminal window
# Run as Administrator
ts-bridge host setup

This configures:

  1. Tailscale unattended mode (tailscale up --unattended)
  2. Tailscale service to auto-start
  3. UPnP services (SSDP, UPnP Device Host)
  4. Tailscale network profile set to Private
  5. RDP enabled via registry
  6. Windows Firewall rule for RDP on TCP 3389
  7. (Optional) Power settings — sleep disabled
Terminal window
# Read-only check
ts-bridge host check
# JSON output for automation
ts-bridge host check --json
Terminal window
ts-bridge host setup --firewall-rule "MyCustomRDPRule"
  • xrdp installed (sudo apt install xrdp)
  • ufw or iptables for firewall
  • Root/sudo privileges
Terminal window
sudo ts-bridge host setup

This configures:

  1. Detects xrdp installation
  2. Opens firewall for RDP port (3389) via ufw or iptables
  3. Prints the Tailscale IP for client configuration
Terminal window
# Read-only check
ts-bridge host check
# JSON output for automation
ts-bridge host check --json

Host setup is not applicable on macOS. macOS machines act as clients only — use ts-bridge connect to connect to a remote Windows or Linux host.

For hosts that cannot run the binary, use the PowerShell setup script:

Terminal window
# Run as Administrator
.\scripts\host\setup.ps1