The terminal solved persistence in 1987 when screen shipped, and nobody noticed, because tmux and screen have been keeping sessions alive across disconnects, reboots of your laptop, and flaky hotel Wi-Fi for decades, and yet the first thing every “web terminal” product tries to sell you is session persistence as if it were a feature they invented rather than a flag on a tool that ships in every distro’s base repository, which tells you the actual gap was never the terminal at all, it was everything around it: who is allowed in, which session they land in, and whether the whole thing is usable from a phone at an airport without handing your shell to the internet.

We hit that gap the way most people do, by wanting to check on a long-running build from the couch and discovering that the honest options were “SSH from a laptop you do not have” or “expose ttyd to the world and hope,” and neither of those is an answer you can give to a second person on the team, so we built Tailshell, a persistent web terminal that wraps ttyd and tmux, puts JWT auth with TOTP in front of the admins, organizes sessions into workspaces, and plays nicely with Tailscale, because the terminal was already done and what it needed was a sane front door.

The terminal was never the problem

tmux is the part of this stack that does the heavy lifting, and it has been doing it since before most web frameworks existed, because a tmux session lives on the server, survives the client vanishing, and lets any number of clients attach to the same view, which means the “collaborative persistent terminal” that products charge money for is, at its core, tmux attach -t main and a willingness to read a man page, and we say that with affection rather than contempt, since building on tmux is the entire reason Tailshell can be small.

ttyd is the other half, a small program that serves a terminal over WebSocket with a proper xterm.js front end, and between the two of them you already have a terminal in a browser that reconnects into a living session, so the engineering question was never “how do we make a web terminal,” it was “how do we put one on a network without creating an incident,” and that is a question about authentication, authorization, and blast radius, not about terminal emulation.

This matters because it changes what you build: when the core is two battle-tested binaries, the new code you write is the thin layer that decides who gets a shell and what they see when they arrive, and thin layers are the ones you can actually audit, which is a property you want in the component that guards remote access to your machines.

What the front end actually adds

Tailshell sits in front of ttyd as the multi-user layer, and the first thing it adds is real authentication, JWT-based sessions for everyone and TOTP as the second factor for admin users, because a web terminal without MFA is a root shell with a URL, and the history of self-hosted tools is littered with exactly that mistake shipped with good intentions, so the authenticator-app enrollment happens at first login rather than living on a roadmap.

The second thing it adds is workspaces, which are named collections of tmux sessions with their own access rules, so the build box, the media server, and the home lab each present as a separate room instead of one long tmux ls output, and each user lands in the workspace they were given rather than in a shell history they should not be reading, which sounds obvious until you remember how many shared tmux servers run on the honor system.

The third thing is quick prompts, a small feature that saves the commands you actually re-run, the deploy, the log tail, the restart incantation, and lets you fire them into a session with a tap, which is the feature that makes the phone-at-the-airport scenario real instead of theoretical, because typing a forty-character kubectl invocation on a touchscreen is the exact moment most people give up and wait until they are home, and a saved prompt turns that into one press on a button you labeled when you were thinking clearly.

Why Tailscale instead of a public port

The default answer for “reach my home server” used to be port forwarding and a prayer, then it became a reverse proxy with Let’s Encrypt and a prayer with better certificates, and both of those put your terminal on the public internet where it gets scanned within the hour, so Tailshell is designed to sit on a Tailscale tailnet, where the machine has a stable private address, the ACLs decide which devices can talk to it at all, and the web UI’s own auth is the second gate rather than the only one, which is defense in depth you get mostly for free because Tailscale did the hard part.

You can still put it behind a reverse proxy if you want, and the JWT plus TOTP stack is built to hold up on its own, but the honest recommendation is the tailnet, since the best attack surface is the one that does not respond to packets from strangers, and a web terminal is a strange thing to volunteer as a public endpoint when the alternative is a mesh network that takes eleven minutes to set up.

What you give up

The tradeoff is that Tailshell is a front end, not a terminal emulator, so your rendering quality is xterm.js, your session semantics are tmux’s, and anything those two do is something Tailshell inherits rather than controls, which we consider a feature on most days and a constraint on the days someone asks for sixel graphics, and it also means multi-user means “multiple people attaching to shared tmux sessions,” which is exactly the right model for admin work and exactly the wrong model if you were hoping for per-user sandboxed shells with resource quotas, a problem we deliberately did not take on.

The modern equivalent of the whole design is the one the industry keeps relearning: SSH plus tmux was the original remote pair-programming and on-call setup, mosh fixed the roaming part for people who live in terminals, and the web UI is just the same idea wearing a browser, so the correct question was never “how do we replace the terminal” but “how do we let a second person through the door safely,” and answering that question honestly is what let Tailshell stay small enough to read in an afternoon, which is the size software that guards your shell should be.