Skip to content

Instantly share code, notes, and snippets.

@maxkueng
Last active March 7, 2026 00:59
Show Gist options
  • Select an option

  • Save maxkueng/34d756e15e855ade2c900db33f9a3ae3 to your computer and use it in GitHub Desktop.

Select an option

Save maxkueng/34d756e15e855ade2c900db33f9a3ae3 to your computer and use it in GitHub Desktop.
Setup tsbridge on Debian

tsbridge on Debian

This guide sets up tsbridge on Debian as a dedicated system user, proxying multiple local services such as 127.0.0.1:3000 and 127.0.0.1:3333 into your Tailscale tailnet.

This setup does not use Docker discovery and does not require root for the running service.

What this gives you

  • One tsbridge process for multiple services
  • Services stay bound to 127.0.0.1
  • Access over Tailscale with HTTPS and MagicDNS names like:
    • https://app.<tailnet>.ts.net
    • https://api.<tailnet>.ts.net
  • A dedicated tsbridge system user
  • A systemd service for automatic startup

Assumptions

  • Debian with systemd
  • Tailscale tailnet already exists
  • Your local apps are already running on localhost
  • Example backends:
    • 127.0.0.1:3000
    • 127.0.0.1:3333
    • 127.0.0.1:8080

1. Install tsbridge

Install Go, then install tsbridge:

sudo apt update
sudo apt install -y golang-go
go install github.com/jtdowney/tsbridge/cmd/tsbridge@latest
sudo install -m 0755 "$HOME/go/bin/tsbridge" /usr/local/bin/tsbridge
/usr/local/bin/tsbridge -h

If you prefer, you can also use an upstream release binary instead of go install.

2. Create a dedicated system user

Create a locked-down service account with its own state directory:

sudo useradd --system --home /var/lib/tsbridge --shell /usr/sbin/nologin tsbridge
sudo mkdir -p /etc/tsbridge
sudo mkdir -p /var/lib/tsbridge
sudo chown tsbridge:tsbridge /var/lib/tsbridge
sudo chmod 0750 /var/lib/tsbridge
sudo chmod 0755 /etc/tsbridge

3. Create a Tailscale OAuth client

Open the Tailscale admin console:

Create an OAuth client with:

Save the values:

  • OAuth client ID
  • OAuth client secret

Important:

  • The secret is shown once.
  • The tag in your tsbridge config must match a tag the OAuth client is allowed to use.

4. Store the OAuth credentials on disk

Create the files:

sudo install -m 0600 -o tsbridge -g tsbridge /dev/null /etc/tsbridge/oauth-id
sudo install -m 0600 -o tsbridge -g tsbridge /dev/null /etc/tsbridge/oauth-secret

Edit them:

sudo nano /etc/tsbridge/oauth-id
sudo nano /etc/tsbridge/oauth-secret

Put only the raw values in the files:

  • /etc/tsbridge/oauth-id: your OAuth client ID
  • /etc/tsbridge/oauth-secret: your OAuth client secret

Why tsbridge:tsbridge ownership?

Because the service runs as the tsbridge user. If you make these files 0600 root:root, tsbridge cannot read them.

5. Create the tsbridge config

Create /etc/tsbridge/tsbridge.toml:

[tailscale]
oauth_client_id_file = "/etc/tsbridge/oauth-id"
oauth_client_secret_file = "/etc/tsbridge/oauth-secret"
state_dir = "/var/lib/tsbridge"
default_tags = ["tag:server"]

[[services]]
name = "app"
backend_addr = "127.0.0.1:3000"

[[services]]
name = "api"
backend_addr = "127.0.0.1:3333"

[[services]]
name = "admin"
backend_addr = "127.0.0.1:8080"

Set ownership and permissions:

sudo chown root:root /etc/tsbridge/tsbridge.toml
sudo chmod 0644 /etc/tsbridge/tsbridge.toml

Notes:

  • name becomes the subdomain on your tailnet.
  • backend_addr points to the local service.
  • Your backends can stay on 127.0.0.1.

6. Validate the config

Run a validation check as the service user:

sudo -u tsbridge /usr/local/bin/tsbridge -config /etc/tsbridge/tsbridge.toml -validate

If that passes, run a foreground test:

sudo -u tsbridge /usr/local/bin/tsbridge -config /etc/tsbridge/tsbridge.toml

From another device on your tailnet, test:

  • https://app.<tailnet>.ts.net
  • https://api.<tailnet>.ts.net
  • https://admin.<tailnet>.ts.net

Stop the foreground process with Ctrl+C once you confirm it starts correctly.

7. Create the systemd service

Create /etc/systemd/system/tsbridge.service:

[Unit]
Description=tsbridge Tailscale reverse proxy
After=network-online.target
Wants=network-online.target

[Service]
User=tsbridge
Group=tsbridge
ExecStart=/usr/local/bin/tsbridge -config /etc/tsbridge/tsbridge.toml
Restart=on-failure
RestartSec=5
WorkingDirectory=/var/lib/tsbridge
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/lib/tsbridge
ReadOnlyPaths=/etc/tsbridge

[Install]
WantedBy=multi-user.target

Reload systemd and start the service:

sudo systemctl daemon-reload
sudo systemctl enable --now tsbridge
sudo systemctl status tsbridge

Watch logs if needed:

sudo journalctl -u tsbridge -f

8. How naming works

With the example config above, the service names map like this:

  • app -> https://app.<tailnet>.ts.net
  • api -> https://api.<tailnet>.ts.net
  • admin -> https://admin.<tailnet>.ts.net

If MagicDNS is enabled in your tailnet, these names should resolve automatically.

9. Common problems

OAuth files are not readable

Symptom:

  • tsbridge fails to start because it cannot read oauth-id or oauth-secret

Fix:

sudo chown tsbridge:tsbridge /etc/tsbridge/oauth-id /etc/tsbridge/oauth-secret
sudo chmod 0600 /etc/tsbridge/oauth-id /etc/tsbridge/oauth-secret

Local backend is unreachable

Check that the backend is actually listening:

ss -ltnp | rg '3000|3333|8080'

Test locally on the server:

curl http://127.0.0.1:3000
curl http://127.0.0.1:3333
curl http://127.0.0.1:8080

Tag errors from Tailscale

If default_tags = ["tag:server"] fails, your OAuth client may not be allowed to create devices with that tag.

Check:

  • the tag selected on the OAuth client
  • your tailnet tag ownership policy

10. Minimal maintenance commands

Restart after config changes:

sudo systemctl restart tsbridge

View recent logs:

sudo journalctl -u tsbridge -n 100 --no-pager

Check whether the service is enabled:

sudo systemctl is-enabled tsbridge

11. Security notes

  • tsbridge is intended mainly for homelab and development use.
  • Keep OAuth secrets out of Git.
  • Keep secrets readable only by the tsbridge user.
  • Using a dedicated system user is better than running the service as root.
  • If you later add Docker discovery, access to /var/run/docker.sock is effectively high privilege.

Example final file layout

/usr/local/bin/tsbridge
/etc/tsbridge/tsbridge.toml
/etc/tsbridge/oauth-id
/etc/tsbridge/oauth-secret
/var/lib/tsbridge/
/etc/systemd/system/tsbridge.service

Sources

  • https://github.com/jtdowney/tsbridge
  • https://tailscale.com/docs/features/oauth-clients
  • https://tailscale.com/docs/reference/trust-credentials
  • https://tailscale.com/docs/reference/key-secret-management
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment