Remote access¶
Run terminux on one machine (a headless server, a desktop tower, a NUC, a Raspberry Pi) and connect from another (your laptop). Your shells live on the server: long-running builds keep going while you switch networks, sleep your laptop, or move to a different room.
The architecture is intentionally thin:
- Server (
--server) is the existing Starlette/uvicorn backend with a persistent auth token and a connect-banner. The full frontend (xterm.js, sidebar, persistence) is served from the same process. - Client (
--connect URL) is a native pywebview window pointed at the server's URL. No backend or PTY runs locally; everything lives on the remote.
The recommended setup: SSH port-forward¶
The token is a 9-letter Google-Meet-style code (e.g. abc-def-ghi),
which is short enough to dictate or paste but not strong enough for
unrestricted exposure on the open internet. Use an SSH tunnel and keep
the server bound to loopback:
uv run terminux --server --port 8443
# prints, prominently:
#
# terminux server listening — connect with:
# http://127.0.0.1:8443/?t=abc-def-ghi
The token is persisted to
~/.local/share/terminux/server.token (Linux) or
~/Library/Application Support/terminux/server.token (macOS) with
mode 0600. The same URL keeps working across server restarts;
bookmark it.
That completes the common case. SSH already solved the hard parts, so there are no new ports, certificates, or auth layers to manage.
Let the app write the commands for you
Running terminux already? Open the command palette (Ctrl+Shift+P on
Linux and Windows, Cmd+Shift+P on macOS) and pick Show remote connection URL. It
reports this machine's name and address, tells you whether a tunnel
is needed, and gives you the ssh -L and terminux --connect lines
with the live port and access code filled in, ready to copy.
Multiple clients¶
The server's WebSocket fan-out supports multiple subscribers per PTY,
so opening a second --connect window from a phone or a second laptop
attaches to the same shells, like tmux attach from several places.
Output appears in every window.
A terminal has a single size, though, and windows rarely match. Each
terminal therefore follows the window you are using: typing in it,
clicking or pasting into it, focusing its window, or switching to its
tab makes that window the owner, and the programs inside redraw for its
size. Only the owner's input reaches the shell. A window you are not
using may look garbled while another one owns the terminal; it snaps
back the moment you use it again. (This is tmux's window-size latest
policy.)
What lives where¶
The server keeps this state across client disconnects, sleeps, and client reboots:
- Workspaces, tabs, their working directories
- Live shells and the processes they're running
- Scrollback (up to 2 MB per tab, replayed on reconnect)
- Sidebar order, font size, UI prefs
The client keeps only per-connection state:
- Window position and size: each launch uses the defaults. Per-client geometry persistence is not implemented in v1.
- Selection, the fuzzy-switcher's typed query, and other in-page state, which reset when the window closes.
File drag-and-drop¶
Dropping a file from Finder / Files / your file manager onto a tab works
the same way in --connect mode as it does locally. Under the hood the
client POSTs the dropped path(s) to POST /api/paste-paths on the
server, which writes the shell-quoted path to the currently active
terminal: the same UX and shell quoting, plus one HTTP round trip.
Bypassing SSH: --host 0.0.0.0¶
If you trust your network (a home LAN, a Tailscale tailnet, a WireGuard mesh) and want a direct connection without setting up SSH forwarding, you can bind the server to a non-loopback address:
The token is now your only line of defense. The 9-letter token plus the per-IP rate limiter (10 failures → 60 s lockout) is enough to frustrate casual brute-force, but it's not enough for raw open-internet exposure. Restrict the route to your trusted network at the OS / router firewall.
For the open internet, terminate TLS upstream (Caddy, nginx, Cloudflare Tunnel) and put a real auth layer in front. terminux itself does not ship TLS support and intentionally keeps the token short.
Security model summary¶
| Setup | Token strength | Recommended use |
|---|---|---|
| SSH tunnel + loopback (default) | Fine: token guards a loopback-bound port | Any remote use over the open internet |
--host 0.0.0.0 on a trusted LAN / VPN |
Fine: 43 bits + per-IP lockout | Home network, Tailscale, WireGuard |
--host 0.0.0.0 on the open internet |
Not enough: front it with a real auth layer | Avoid unless you've layered something in front |
Lifecycle / running it as a service¶
--daemon: start it and get your prompt back¶
-d / --daemon starts the server (port 8443 unless you pass --port), waits until it answers, prints the URL to connect to, and goes to the background. With --server it's headless; without, the desktop window opens as usual, from the background process, and the printed URL lets other machines connect to it too. --public-host sets the name in that URL; without it, a --host 0.0.0.0 server prints this machine's network name, and a loopback server prints 127.0.0.1 for use through an SSH tunnel. If the server can't start (the port is taken, say), the command says why and exits with an error instead of leaving a dead process behind.
The background process logs to ~/Library/Logs/terminux/terminux.log on macOS and ~/.local/state/terminux/log/terminux.log on Linux; its output streams (fault dumps, crash tracebacks) go to the same file. Its PID is in terminux.pid, in ~/Library/Application Support/terminux/ on macOS and ~/.local/state/terminux/ on Linux. Stop it with terminux --stop, which closes its window, if it has one, and waits until it's gone (kill <pid> works too); closing the window stops it as well. While a daemon runs, starting another one fails with its PID, since both would share the same saved state. --daemon isn't available on Windows.
With a service manager¶
To start it at boot or restart it after a crash, let the system's service
manager run terminux --server in the foreground (not --daemon):
- systemd user service: a single
WantedBy=default.targetunit that runsuv run terminux --server. The token file persists across service restarts, so your bookmarked URL keeps working. - tmux session:
tmux new-session -d -s terminux 'uv run terminux --server'. It's trivial to set up and keeps running after logout. - macOS launchd: a per-user
LaunchAgentplist if you want it to auto-start at login.
Pick whichever matches the host's idiom. The watchdog and --debug /
--trace flags described in Debugging knobs
work the same in server mode. In the foreground they write to stderr,
which systemd and launchd capture; with --daemon they go to the log
file.
Known limitations (v1)¶
- No predictive local echo. Each keystroke is a WebSocket frame to the server; on a high-latency WAN (50 ms+ RTT) typing will feel slightly laggy. For high-latency cases, SSH + mosh + tmux gives better typing UX. terminux's strength is the workspace/tab model on LAN or low-latency WAN.
- No transport security in terminux itself. Use SSH or a TLS
terminator upstream. Do not run
--host 0.0.0.0straight on the open internet. - Per-client window geometry is not persisted yet: every
--connectlaunch opens at default size. - No "rotate token" CLI:
rm ~/.../server.tokenand restart the server to force a new one. The previous URL stops working.