Skip to content

bobtheskull-flameeyes/ts-proxy

Repository files navigation

ts-proxy

A tsnet-powered SOCKS5 + HTTP CONNECT proxy daemon. Joins a Tailscale tailnet as a lightweight userspace node and exposes local proxy endpoints, letting any tool (curl, Python, Go programs, etc.) access tailnet nodes without embedding tsnet themselves.

The binary is fully self-contained — no shell scripts required.

Endpoints

Service Address Protocol
SOCKS5 127.0.0.1:1080 SOCKS5 proxy
HTTP CONNECT 127.0.0.1:4444 HTTP CONNECT proxy
Health check 127.0.0.1:4445 GET /health → 200

First-Time Setup

1. Generate a Tailscale auth key

Go to the Tailscale admin console and create an auth key. A reusable, ephemeral key works well.

2. Authenticate

TS_AUTHKEY=tskey-auth-xxxxx ./ts-proxy auth

This joins the tailnet, persists state to ./state/, then exits. You only need to do this once — state survives restarts.

3. Set up the keepalive cron

Create a single cron job that runs ts-proxy ensure every 2 minutes. The ensure subcommand handles everything: cold start, health monitoring, proxy token rotation (for container recycles), and stale state cleanup.

*/2 * * * * /path/to/ts-proxy ensure

For example, as a cron definition:

---
id: ts-proxy-keepalive
enabled: true
mode: heartbeat
schedule:
  kind: interval
  every: 2m
  at: "2025-01-01T00:00:00Z"
  timezone: UTC
---
/path/to/ts-proxy ensure

That's it. The ensure command handles:

  • Cold start — if no daemon is running, starts one
  • Health check — verifies the daemon is responding
  • Proxy token rotation — detects container recycles that change outbound proxy credentials, restarts the daemon with fresh creds
  • Stale state cleanup — removes leftover temp files from crashed sessions

4. Done

From here it self-heals. The cron job will restart the daemon after container recycles, OOM kills, or any other failure.

Subcommands

Command Description
ts-proxy auth One-time authentication (requires TS_AUTHKEY)
ts-proxy serve Start the proxy daemon (default if no subcommand)
ts-proxy ensure Idempotent keepalive — put this in cron

Environment Variables

Variable Required Default Description
TS_AUTHKEY First auth only Tailscale auth key for initial registration
TS_PROXY_HOSTNAME No bob-proxy tsnet node hostname on your tailnet
TS_VERBOSE No 0 Set to 1 for verbose tsnet logging
GOMEMLIMIT No 100MiB Go memory limit (binary sets a sane default)
GOGC No 50 Go GC target percentage

Usage Examples

curl via SOCKS5

curl --socks5-hostname 127.0.0.1:1080 http://your-device.your-tailnet.ts.net:8123/api/

curl via HTTP CONNECT

curl --proxy http://127.0.0.1:4444 http://your-device.your-tailnet.ts.net:8123/api/

Python requests

import requests

proxies = {
    "http": "socks5h://127.0.0.1:1080",
    "https": "socks5h://127.0.0.1:1080",
}
r = requests.get("http://your-device.your-tailnet.ts.net:8123/api/", proxies=proxies)

Environment variables (global proxy)

export ALL_PROXY=socks5h://127.0.0.1:1080
export HTTPS_PROXY=http://127.0.0.1:4444

Flags

Flag Default Description
-socks 127.0.0.1:1080 SOCKS5 listen address
-http 127.0.0.1:4444 HTTP CONNECT listen address
-health 127.0.0.1:4445 Health check listen address
-state ./state State directory path
-verbose false Enable verbose tsnet logging

What It Does Under the Hood

On serve startup:

  1. Sets memory defaultsGOMEMLIMIT=100MiB, GOGC=50 (overridable via env)
  2. Ensures host0 IPv4 — adds a dummy 10.0.99.1/32 to the host0 interface if needed (container-specific: tsnet needs to see v4=true)
  3. Cleans stale state — removes tailscaled.state.tmp* from previous crashes
  4. Pre-flight connectivity check — verifies controlplane.tailscale.com is reachable through the platform proxy. If unreachable, exits with a clear error instead of hanging for 90 seconds
  5. Starts tsnet — with a retry loop (up to 5 attempts) to handle transient v4/v6 detection races
  6. Writes PID filestate/serve.pid for ensure to find and manage
  7. Opens listeners — SOCKS5, HTTP CONNECT, health check
  8. Handles SIGTERM/SIGINT — graceful shutdown

On ensure (cron):

  1. Checks health — quick HTTP call to the health endpoint
  2. Compares proxy tokens — reads $https_proxy from its fresh env, compares against state/proxy_token saved from last start
  3. Decides:
    • Healthy + token unchanged → exit 0 (nothing to do)
    • Healthy + token changed → kill existing daemon, start new one
    • Not healthy → clean up, start fresh
  4. Waits up to 90s for the new daemon to become healthy
  5. Saves the current proxy token on success

Use Cases

  • Home Assistant — reach your HA instance on the tailnet from this container
  • IMAP/SMTP — connect to a mail server on your tailnet
  • SSH — proxy SSH connections to tailnet hosts
  • Any tailnet service — anything reachable on your tailnet becomes reachable from this container

State Directory

The ./state/ directory contains:

  • tsnet auth — persisted Tailscale credentials
  • serve.pid — PID of the running daemon
  • proxy_token — last-known platform proxy auth token

Place it somewhere that survives container restarts (e.g. your workspace). If the state is lost, re-run ./ts-proxy auth with a fresh TS_AUTHKEY.

Architecture

┌──────────────────────────────────────────┐
│  ts-proxy daemon (serve)                 │
│                                          │
│  tsnet.Server ("bob-proxy")            │
│       │                                  │
│       ├─ SOCKS5 server  ←─ :1080         │
│       ├─ HTTP CONNECT   ←─ :4444         │
│       └─ Health check   ←─ :4445         │
│                                          │
│  All outgoing connections use srv.Dial() │
│  → direct tailnet routing, no exit node  │
└──────────────────────────────────────────┘
         ↕ WireGuard tunnel
┌──────────────────────────────────────────┐
│  Your tailnet nodes                      │
│  - Home Assistant, NAS, mail server,     │
│    dev machines, etc.                    │
└──────────────────────────────────────────┘

┌──────────────────────────────────────────┐
│  ts-proxy ensure (cron, every 2m)        │
│                                          │
│  1. Check /health                        │
│  2. Compare proxy token (fresh env)      │
│  3. Kill + restart if needed             │
│  4. Wait for healthy                     │
│  5. Save proxy token                     │
└──────────────────────────────────────────┘

Building from Source

GOOS=linux GOARCH=amd64 go build -o ts-proxy .

Requires Go 1.22+ and the tailscale.com/tsnet module.

About

Tailscale SOCKS5/HTTP CONNECT proxy for containerised environments

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors