Documentation

Database & GUI clients

Point a database GUI (TablePlus, DBeaver, …) at a tailnet host through the profile that owns its suffix — without a system-wide Tailscale client.

View as Markdown

Quick answer#

Give the client a fixed loopback endpoint instead of the tailnet hostname:

  • In the app — menu bar → Open → Tunnels → Add Tunnel, target db.internal.example:5432, copy the assigned 127.0.0.1:<port> into TablePlus. Always-on, managed for you.
  • From the command line tailmux tunnel --listen 127.0.0.1:15432 db.internal.example:5432, then connect to 127.0.0.1:15432. Lives as long as the command runs.

Either way, TailMux resolves and dials the target inside the owning profile's tailnet and relays the bytes both ways — pure transport, like an SSH port-forward.

In the app#

  • Click the TailMux menu bar icon → OpenTunnels Add Tunnel.
  • Pick the profile (or leave it to the target's suffix), name the tunnel, and set the Target to the database's host:port — for example db.internal.example:5432 for Postgres.
  • Leave the Local endpoint blank: TailMux assigns a fixed free port from the tunnel base (default 43300, configurable under Settings → Profile defaults). The port is stable across restarts, so you configure the client once.
  • Click the copy button on the tunnel row and paste the endpoint into your client — e.g. in TablePlus set Host 127.0.0.1 and Port to the copied port.
Tunnel
1Profile:  work2Target:   db.internal.example:54323Endpoint: 127.0.0.1:43301   (assigned for you — copy with one click)

The tunnel starts automatically whenever its profile is running and appears in the menu bar with a start/stop switch and a live connection count. Tunnel edits apply live — no router restart needed.

Database only reachable through a jump host? Turn on Forward through an SSH bastion when adding the tunnel and set the bastion (e.g. bastion.example.ts.net), the SSH user, and how to authenticate — ssh-agent, a private key, or a password stored in the macOS Keychain. TailMux keeps a background ssh -L up for you. Details on the tunnels page.

From the command line#

The same endpoint from a terminal — for scripts, for Linux, or for a one-session tunnel that ends when you close it:

zsh — tailmux
$tailmux tunnel --listen 127.0.0.1:15432 db.internal.example:5432

The profile is selected automatically from the host's suffix; pass --profile <name> to force one (required for raw-IP targets). The tunnel lives for as long as the command runs. Swap the port for your engine — 3306 MySQL, 1433 SQL Server, 27017 MongoDB, 6379 Redis.

Connect a terminal client to the listener:

zsh — tailmux
$psql "postgresql://app@127.0.0.1:15432/appdb"

Client settings#

With a tunnel running (app or CLI), point the client at the loopback endpoint:

tailmux
1Host:     127.0.0.12Port:     43301           (the tunnel's assigned or chosen port)3Database: <your database>4User:     <your user>

Nothing about the database itself changes — credentials, TLS to the server, and permissions are exactly what they'd be from inside the tailnet.

Why the client fails without a tunnel#

A database client opens a raw TCP connection and first asks the operating system to resolve the hostname. TailMux stays at the proxy layer — it never installs a TUN device and never touches the system resolver — so a tailnet-internal name like db.internal.example is invisible to macOS: the lookup hits public DNS, returns NXDOMAIN, and the client fails before TailMux is ever involved. GUI clients also ignore HTTP_PROXY / ALL_PROXY, so tailmux run can't help either. The tunnel removes both problems: the client only ever dials 127.0.0.1, and TailMux does the resolving and dialing inside the right profile.

Why diagnostics still passes#

The Diagnostics check reporting the route as successful for db.internal.example means TailMux knows which profile owns that suffix and can dial it — resolution and dial happen inside TailMux's process, through the profile's own tailnet DNS. It does not mean your Mac's other apps can reach it. The diagnostic confirms ownership and reachability; the tunnel is what puts your client onto that route.

Notes#

  • The owning profile must list the suffix (e.g. .internal.example) under its suffixes, and — when the host sits behind a tailnet subnet router — have accept routes enabled.
  • Adding a suffix is a routing change: it applies on router restart. The app prompts you to restart after saving. Tunnel edits themselves are live.
  • The profile must be logged in before the dial succeeds.
  • Keep one endpoint per service (the app does this for you) so saved connections in your client keep working across restarts.
  • The same pattern covers any raw-TCP service — RDP, SMB, Redis. For interactive SSH shells, use tailmux ssh instead of a tunnel.