Why this exists.
tsproxyis a minimal, self-contained Tailscale client built for emulated/sandboxed Linux environments (Alpine-based containers, iSH-style userspace kernels, AI-agent sandboxes, CI runners, etc.) where the officialtailscaleddaemon often cannot run — fulltundevice access,AF_NETLINKsockets, orCAP_NET_ADMINmay be unavailable or restricted. Because it's built on the tsnet library instead of the system daemon, it only needs plain outbound socket access to join a tailnet. This makes it a practical way to get an AI agent (or any other sandboxed process) authorized onto your Tailscale network in seconds, and reach your other tailnet devices through a local SOCKS5/HTTP proxy — no root, no kernel modules, no system-leveltailscaledinstall.
Built on the tsnet library, tsproxy
is a standalone Tailscale client that does not require installing or
depending on the tailscaled system daemon. It runs a userspace Tailscale
node directly inside its own process, and exposes that node's network
connectivity as local SOCKS5 and HTTP/HTTPS forward proxies, so any
other program (browsers, curl, other CLI tools, etc.) can reach your tailnet
simply by pointing at the proxy.
It's especially well suited to sandboxed environments running minimal
distros like Alpine (userspace kernel emulators such as iSH, AI-agent
sandboxes, container CI runners) — environments that often lack full tun
device access, AF_NETLINK sockets, or CAP_NET_ADMIN capability, making
the official tailscaled daemon hard or impossible to run. tsproxy only
needs ordinary outbound socket access to join a tailnet, letting a process
running inside such a sandbox (e.g. an AI agent) get authorized in seconds.
- Single static binary, zero system dependencies (no root, no
tailscaledinstall) - Runs both a SOCKS5 and an HTTP/HTTPS proxy simultaneously, each with optional username/password or Basic Auth authentication
- Friendly interactive auth flow: on first run it prints a clickable hyperlink (OSC 8 — clickable in iTerm2, Windows Terminal, and most modern terminals), renders a terminal ASCII QR code, and generates a PNG QR code image (handy to send/display for phone scanning), and attempts to open your system's default browser automatically — everything happens in the terminal, no digging through logs for the auth URL
- Supports silent login via a pre-authorized Auth Key, ideal for servers/CI/container use cases
- Supports a self-hosted control plane such as Headscale (
-control-url) - Supports exit nodes:
-exit-noderoutes all non-tailnet public traffic through a chosen tailnet device (the target must have already runtailscale set --advertise-exit-nodeand been approved by the tailnet admin) - Supports accepting subnet routes and other advanced Tailscale features
- Node identity is persisted in a local state directory, so restarting the process doesn't require re-authorization
See the release artifacts (tsproxy-<os>-<arch>); download and chmod +x to run.
git clone <this-repo>
cd tsnet-proxy
go build -o tsproxy ./cmd/tsproxyRequires Go 1.23+ (the latest tsnet requires Go 1.26+; if your Go toolchain
is older, go.mod is pinned to tailscale.com v1.102.4, which is compatible
with Go 1.23).
# Start the proxy (first run walks you through authorization)
./tsproxy up
# Customize listen addresses / node name
./tsproxy up -hostname my-laptop -socks5 127.0.0.1:1080 -http 127.0.0.1:8080
# Silent login with a pre-authorized key (no interaction needed)
TS_AUTHKEY=tskey-auth-xxxxx ./tsproxy up
# Add authentication to the proxies
./tsproxy up -socks5-user alice -socks5-pass s3cret -http-user alice -http-pass s3cret
# Check node status
./tsproxy status
# Log out (clears the local node identity)
./tsproxy logoutOn startup you'll see output like:
🧦 SOCKS5 proxy ready: socks5://127.0.0.1:1055
🌐 HTTP/HTTPS proxy ready: http://127.0.0.1:1056
Other programs can then use it like this:
curl -x socks5h://127.0.0.1:1055 http://some-tailnet-host/
curl -x http://127.0.0.1:1056 https://some-tailnet-host/
export ALL_PROXY=socks5h://127.0.0.1:1055
export HTTP_PROXY=http://127.0.0.1:1056
export HTTPS_PROXY=http://127.0.0.1:1056On first run of tsproxy up (or if local state was cleared / the key
expired), the program will:
- Request a one-time authorization URL from the Tailscale control plane
(looks like
https://login.tailscale.com/a/xxxxxxxxxxxx) - Print that URL in the terminal and render a QR code
- Try to open the URL automatically in your system's default browser
- Poll authorization status in the background; once you complete login in the browser/phone, the program detects it and continues starting the proxy
If your tailnet has device approval enabled, an admin may also need to approve the device manually in the Tailscale admin console after the auth link is used — the program will prompt you about this step.
The -state-dir (default ~/.tsproxy/state) holds this device's node
identity key in your tailnet. Anyone who obtains this file can impersonate
this device on your network — treat it like an SSH private key or a cloud
credential.
- The directory is created automatically by the
tsnetlibrary with0700permissions (directory) /0600(files), readable/writable only by the current user. This has been verified experimentally; no extra hardening is needed on top of it. - Never commit the
-state-dirdirectory to Git, bake it into a Docker image, or share it in any way. - For a one-off trial or CI usage, pass
-ephemeral: the node is automatically removed from your tailnet when the process exits, leaving no long-lived credential behind.
| flag | default | description |
|---|---|---|
-hostname |
local hostname | Node name shown in the tailnet |
-state-dir |
~/.tsproxy/state |
Directory for persisting node identity/keys |
-authkey |
$TS_AUTHKEY |
Pre-authorized key, skips interactive login |
-control-url |
$TS_CONTROL_URL |
Self-hosted control plane (e.g. Headscale) address |
-ephemeral |
false | Ephemeral node; automatically removed from the tailnet on exit |
-socks5 |
127.0.0.1:1055 |
SOCKS5 listen address; empty disables it |
-http |
127.0.0.1:1056 |
HTTP/HTTPS proxy listen address; empty disables it |
-socks5-user / -socks5-pass |
empty | SOCKS5 username/password authentication |
-http-user / -http-pass |
empty | HTTP proxy Basic Auth authentication |
-exit-node |
empty | Use the given exit node; all outbound traffic routes through it (requires the target to advertise it and be approved by the admin) |
-exit-node-allow-lan-access |
false | Still allow access to the local LAN while using an exit node |
-accept-routes |
true | Accept subnet routes advertised by other nodes |
-verbose |
false | Print verbose logs, including tsnet's internal logs |
-qr-file |
state-dir/auth-qr.png |
Path to save the auth QR code image; pass - to disable |
cmd/tsproxy/ CLI entry point (up/status/logout/version subcommands)
internal/config/ Command-line flag parsing and defaults
internal/proxy/ SOCKS5 and HTTP/HTTPS forward proxy implementation (stdlib only, dials out via tsnet.Dial)
internal/tsclient/ tsnet.Server wrapper + interactive auth flow (link/QR/auto-open browser)
internal/version/ Version and build metadata reporting
GOOS=linux GOARCH=amd64 go build -o dist/tsproxy-linux-amd64 ./cmd/tsproxy
GOOS=linux GOARCH=arm64 go build -o dist/tsproxy-linux-arm64 ./cmd/tsproxy
GOOS=darwin GOARCH=amd64 go build -o dist/tsproxy-darwin-amd64 ./cmd/tsproxy
GOOS=darwin GOARCH=arm64 go build -o dist/tsproxy-darwin-arm64 ./cmd/tsproxy
GOOS=windows GOARCH=amd64 go build -o dist/tsproxy-windows-amd64.exe ./cmd/tsproxyMIT — see LICENSE. tsproxy itself only uses MIT-licensed
dependencies (qrterminal,
go-qrcode); the upstream
tailscale.com module it embeds via
tsnet is licensed under BSD-3-Clause.