https://github.com/jtdowney/tsbridge
A lightweight proxy manager built on Tailscale's tsnet library that enables multiple HTTPS services on a Tailnet
https://github.com/jtdowney/tsbridge
homelab networking proxy tailscale
Last synced: 4 months ago
JSON representation
A lightweight proxy manager built on Tailscale's tsnet library that enables multiple HTTPS services on a Tailnet
- Host: GitHub
- URL: https://github.com/jtdowney/tsbridge
- Owner: jtdowney
- License: mit
- Created: 2025-06-21T21:48:44.000Z (about 1 year ago)
- Default Branch: main
- Last Pushed: 2026-01-19T04:52:42.000Z (5 months ago)
- Last Synced: 2026-01-19T12:48:55.368Z (5 months ago)
- Topics: homelab, networking, proxy, tailscale
- Language: Go
- Homepage:
- Size: 1.46 MB
- Stars: 246
- Watchers: 4
- Forks: 8
- Open Issues: 11
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
- Threat model: THREAT_MODEL.md
- Security: SECURITY.md
- Agents: AGENTS.md
Awesome Lists containing this project
README
# tsbridge
[](https://github.com/jtdowney/tsbridge/releases)
[](https://github.com/jtdowney/tsbridge/actions)
[](https://goreportcard.com/report/github.com/jtdowney/tsbridge)
[](https://github.com/jtdowney/tsbridge/blob/main/LICENSE)

tsbridge acts as a tsnet-powered reverse proxy, letting you expose multiple backend services on your Tailnet from a single process. It's designed for homelabs and development environments where you want the magic of Tailscale without the hassle of running a separate sidecar for every service.
Inspired by Traefik, tsbridge can be configured with a simple TOML file or by watching Docker for container labels.
## Why?
I got tired of spinning up a new [tsnsrv](https://github.com/boinkor-net/tsnsrv) instance for every service I wanted to expose on my Tailnet. Each one needs its own systemd service and configuration. With tsbridge, you configure once and add services as needed - either through a config file or by just adding labels to your Docker containers.
## Install
Grab a binary from [releases](https://github.com/jtdowney/tsbridge/releases) or:
```bash
go install github.com/jtdowney/tsbridge/cmd/tsbridge@latest
```
## Quick start
1. Get OAuth credentials from
2. Create `tsbridge.toml`:
```toml
[tailscale]
oauth_client_id_env = "TS_OAUTH_CLIENT_ID"
oauth_client_secret_env = "TS_OAUTH_CLIENT_SECRET"
[[services]]
name = "api"
backend_addr = "127.0.0.1:8080"
[[services]]
name = "web"
backend_addr = "unix:///var/run/web.sock"
```
3. Run it:
```bash
export TS_OAUTH_CLIENT_ID=your-id
export TS_OAUTH_CLIENT_SECRET=your-secret
tsbridge -config tsbridge.toml
```
tsbridge will now be available on your tailnet. Thanks to MagicDNS, you can reach your services at `https://api..ts.net` and `https://web..ts.net`.
## Configuration
tsbridge is configured via `tsbridge.toml`. See [docs/quickstart.md](docs/quickstart.md) for getting started quickly, or [docs/configuration-reference.md](docs/configuration-reference.md) for all options.
Here are a few common settings:
- `whois_enabled`: Set to `true` to add `Tailscale-User-*` identity headers to upstream requests
- `write_timeout`: Defaults to `30s`. Set to `"0s"` to support long-running connections like Server-Sent Events (SSE)
- `metrics_addr`: Expose a Prometheus metrics endpoint (e.g., `":9090"`) - see [docs/metrics.md](docs/metrics.md) for available metrics (secure this endpoint in production)
### Security
> **Security Note**: tsbridge is intended for homelabs and development environments. It hasn't been hardened or battle-tested for production workloads. See [THREAT_MODEL.md](THREAT_MODEL.md) for details.
For enhanced security using tag ownership, see [Tag Ownership and OAuth Security](docs/configuration-reference.md#tag-ownership-and-oauth-security).
## Docker
### Static config
```bash
docker run -v /path/to/config:/config \
-e TS_OAUTH_CLIENT_ID=... \
-e TS_OAUTH_CLIENT_SECRET=... \
ghcr.io/jtdowney/tsbridge:latest -config /config/tsbridge.toml
```
### Dynamic with labels (like Traefik)
tsbridge can watch Docker and automatically expose containers based on their labels:
```yaml
# docker-compose.yml
services:
tsbridge:
image: ghcr.io/jtdowney/tsbridge:latest
command: ["--provider", "docker"]
volumes:
- /var/run/docker.sock:/var/run/docker.sock # Required for label discovery
- tsbridge-state:/var/lib/tsbridge
environment:
- TS_OAUTH_CLIENT_ID=${TS_OAUTH_CLIENT_ID}
- TS_OAUTH_CLIENT_SECRET=${TS_OAUTH_CLIENT_SECRET}
labels:
- "tsbridge.tailscale.oauth_client_id_env=TS_OAUTH_CLIENT_ID"
- "tsbridge.tailscale.oauth_client_secret_env=TS_OAUTH_CLIENT_SECRET"
- "tsbridge.tailscale.state_dir=/var/lib/tsbridge"
- "tsbridge.tailscale.default_tags=tag:server" # Must match or be owned by your OAuth client's tag
whoami:
image: traefik/whoami
labels:
- "tsbridge.enabled=true"
- "tsbridge.service.name=whoami"
- "tsbridge.service.port=80"
volumes:
tsbridge-state:
```
See [docs/docker-labels.md](docs/docker-labels.md) for the full label reference.
> **Note**: The `default_tags` must match or be owned by your OAuth client's tag. Individual services can override this with their own `tags` label. See [Tag Ownership and OAuth Security](docs/configuration-reference.md#tag-ownership-and-oauth-security) for setup details.
## Headscale
Works with [Headscale](https://headscale.net/) but requires auth keys instead of OAuth and TLS must be disabled (until [headscale issue #2137](https://github.com/juanfont/headscale/issues/2137) gets implemented):
```toml
[tailscale]
auth_key_env = "TS_AUTH_KEY"
control_url = "https://headscale.example.com"
[[services]]
name = "api"
backend_addr = "127.0.0.1:8080"
tls_mode = "off" # Required for Headscale
```
See [example/headscale/](example/headscale/) for a complete setup.
## Development
```bash
make build # Build binary
make test # Run tests
make lint # Run linters
```
## Monitoring
When `metrics_addr` is configured, tsbridge exposes Prometheus metrics at `http://`:
- Request counts and latencies
- Error rates
- Active connections
- Service lifecycle events
## Architecture
Each service runs its own tsnet instance with isolated state, enabling independent lifecycle management and per-service configuration.
## Prior Art
- [tsnsrv](https://github.com/boinkor-net/tsnsrv) - Inspiration but single service focused
- [tsdproxy](https://github.com/almeidapaulopt/tsdproxy) - Docker-specific
## License
MIT License - see [LICENSE](LICENSE) file for details.