Self-hosted service
tsl0922/ttyd avatar
tsl0922/ttyd

ttyd: a terminal you hand to somebody over HTTP

Share your terminal over the web

12,492 stars1,209 forksCMIT

At a glance

What is it?
A C program that runs a command, puts it behind a WebSocket, and draws it in the browser with xterm.js. Useful on routers and jump hosts, blunt about what it does not protect.
Who is it for?
ttyd is at its best on hardware with no display and no SSH port exposed: an OpenWrt router, a container, a bastion with a private network path. It is the right tool when you want read access for someone, one command, and a browser.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 61 days ago.
What is it written in?
Mainly C, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 11, 2026, and from our analysis. They are not legal advice.

Editorial analysis

One process, one command, one port

The whole idea is compressed into a single line of C. ttyd forks the command you name, allocates a pty for it, and relays bytes between that pty and a WebSocket, while the browser side draws the result with xterm.js. The README credits libuv for the event loop and WebGL2 for rendering speed, which is an unusual pair of claims for a terminal emulator and explains why the binary is small enough to install on a router.

The usage line is the whole API:

bash
ttyd [options] <command> [<arguments...>]

There is no config file, no daemon to supervise and no database. You start the process, it listens, and it stops when the process stops. That is a genuine advantage over the alternatives the README names, Wetty, which is Node based and needs an npm install tree, and GoTTY, which is a single Go binary in much the same way ttyd is. ttyd's own description is deliberately modest: a simple command-line tool for sharing a terminal over the web.

The topic list on the repository carries the practical signal. Alongside the obvious terminal and websocket tags sit `openwrt`, `ssl`, `tty` and `xterm`, which is the same set of words you would use when describing a lightweight web console for embedded devices.

Package managers for every platform the README names

Installation is mostly delegated to the platform package manager, which is the fastest way to tell whether a project has settled. macOS gets a Homebrew formula and a MacPorts port. Debian and Ubuntu get a native package, and OpenWrt gets an `opkg` package, which is the case most people do not expect and the one that justifies the project existing.

bash
brew install ttyd
sudo apt install ttyd
opkg install ttyd
winget install tsl0922.ttyd

Windows is documented three ways: a binary from the releases page, WinGet, or Scoop. Gentoo is the odd one out, with instructions to clone a third party repository into a local overlay rather than an official ebuild, and the README links out to the Gentoo wiki for how that works.

The last resort on every platform is a precompiled static binary from the releases page, and that page is also where you find out how current the project is. The newest release listed there is 1.7.7, published on 2024-03-30, while the last push to the default branch is dated 2026-08-12. Those two facts sit awkwardly together. Commits are landing on main long after the last tagged build, which usually means development happens on main and releases are cut when there is a reason. The 1.7.7 notes say as much: it fixes version detection for people not building from a git checkout, and explicitly says you do not need to update if that does not bother you.

Read-only by default, and writable only on request

The default posture of the tool is worth knowing before you paste anything into a browser. Without flags, clients can watch the session but cannot type into it. The option that changes this is `-W`, and the option that limits how many people can watch at once is `-m`, which defaults to no limit at all. If you hand the URL to a colleague and they only need to see the output, you never have to think about write access.

bash
ttyd -p 7681 -m 4 -W bash

Two other flags exist for the lifetime of the session. `-o` accepts exactly one client and exits when that client disconnects, which suits a scripted grab of an interactive installer. `-q` exits when the last client disconnects, which suits a temporary console on a device you are about to power cycle.

Authentication is the part where the README is candid by omission. There is `-c` for a single `username:password` pair, which is one shared secret for everyone, not an account system. There is also `-H`, which does something more useful in practice: it names an HTTP header and tells ttyd to defer authentication to a reverse proxy that sets that header. For anything beyond a trusted network, that is the flag that matters, because it lets an existing identity provider sit in front without ttyd pretending to have user management.

Reverse proxy flags, origin checking and TLS

Running ttyd behind nginx or Caddy needs two settings, and both are in the option list. `-b` declares the base path the proxy will serve from, up to 128 characters, so ttyd knows the prefix to generate links with. `-O` refuses WebSocket connections whose Origin header does not match, which is a browser-side protection against a page on another origin opening a socket to your terminal.

bash
ttyd -b /mounted/here -O -p 7681 bash
ttyd -c username:password -W bash

The first line is a normal proxied deployment, using the README’s own example path. The second is the built-in authentication, which takes a single `username:password` pair and opens the terminal for writing. For anything shared between more than one person, `-H` is the better option, because it names an HTTP header and tells ttyd to let a reverse proxy handle authentication instead.

For TLS there is `-S` to enable it and then `-C` and `-K` for the certificate and key, plus `-A` for a CA file when clients present certificates. The library behind it is OpenSSL or Mbed TLS, selectable at build time. Nothing is required: with no SSL flags, ttyd serves plain HTTP and the WebSocket carries your shell session in the clear. That is fine on localhost and wrong on any network you do not control, and the fix is either `-S` with real certificate files or terminating TLS at the proxy.

There are smaller switches worth knowing. `-T` sets the reported terminal type, defaulting to `xterm-256color`, which matters when a program changes behaviour based on what TERM claims to be. `-t` sends key/value options through to the client and can be repeated. `-P` sets the WebSocket ping interval in seconds, default 5, which is the knob to reach for when an intermediary times out idle terminals.

ZMODEM, trzsz and Sixel go further than a plain shell

A remote terminal you can only type into is half a tool. ttyd's feature list leans into file transfer and image output, which is where it separates from a bare pty over a socket.

File transfer is handled two ways. ZMODEM, with lrzsz on the remote side, is the older mechanism and works with existing scripts. trzsz is the modern replacement, and the 1.7.5 release notes show it maturing: a `trzszDragInitTimeout` client option was added in that release. Sixel output is supported through img2sixel or lsix, which means a command that can draw in sixel renders as an image in the browser instead of dumping escape codes you cannot read.

The browser side handles wide characters and IME input, so a shell on a machine with a Chinese locale types correctly rather than as mojibake. The 1.7.5 notes also record Safari mobile full screen support and an option to override client options through URL query parameters, which is convenient for demos and one more thing to think about before you leave a session open on a shared machine.

Containerised runs and what the wiki covers

The repository ships two Dockerfiles. The default one is Ubuntu based, installs `tini` as an init process, copies a prebuilt `ttyd` into `/usr/bin`, exposes port 7681 and sets the working directory to `/root`. Its command line is `ttyd -W bash`, which means a container run this way hands the browser a writable root shell.

bash
FROM ubuntu:24.04
EXPOSE 7681
CMD ["ttyd", "-W", "bash"]

An Alpine variant sits next to it, which matters for small deployments. For a router or a minimal VM, `opkg install ttyd` is a better path than any container.

The repository tree shows where the work lives: `src/` for the server, `html/` for the browser client, `cmake/` for the build, `man/` for the man page, and `snap/`, `scripts/` and `app.rc.in` for packaging across platforms. The README is explicit that its own command-line list is not the documentation. It points to the Example Usage page on the project wiki for real configurations, which is where you should go once you are past localhost, because that page is where proxy setups, systemd units and TLS configurations actually live. The README also defers browser compatibility to xterm.js's own support list, which is the correct answer for a project that embeds it rather than maintains a table of its own.

The licence is MIT, so embedding ttyd in an appliance, or shipping it inside a product image, carries no copyleft obligation. What it does carry is a security obligation the licence cannot express: the tool is a shell, and it will faithfully run whatever command it is pointed at for whoever can reach the port.

Editorial conclusion

ttyd is at its best on hardware with no display and no SSH port exposed: an OpenWrt router, a container, a bastion with a private network path. It is the right tool when you want read access for someone, one command, and a browser. It is the wrong tool for anything multi-user with real identity, because basic authentication is a single shared credential and the WebSocket carries a live shell. Verify two things before exposing it: that `-H` points at a proxy that actually authenticates, or that `-S` with a real certificate is doing TLS, and that the process it starts is a command you would be comfortable running as another person. Start on localhost, then read the Example Usage wiki page, which the README points to and does not reproduce.

Frequently asked questions

What is ttyd?

ttyd is a C program that runs a command in a pty, relays it over a WebSocket, and renders it in the browser with xterm.js. You start it with a command such as `ttyd bash` and it listens on port 7681 by default. The browser terminal is read-only unless you pass the writable flag.

How do I put ttyd behind nginx with authentication?

Pass `-b` with the path prefix the proxy serves from and `-H` with the name of a header your proxy sets after authenticating the user, for example `ttyd -b /terminal -H X-Auth-User bash`. The README says the auth header option makes ttyd let a HTTP reverse proxy handle authentication. Add `-O` to reject WebSocket connections from a different origin.

How does ttyd compare with Wetty and GoTTY?

The README lists both as alternatives and names their difference in one line each: Wetty is a Node based web terminal oriented at SSH and login flows, and GoTTY is a Go based web terminal. ttyd is the C implementation of the same idea, with the extras the README lists: ZMODEM and trzsz file transfer, Sixel image output, IME support and TLS.

Official sources

  1. License: MIT
  2. Project website
  3. README
  4. Releases
  5. tsl0922/ttyd on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/tsl0922-ttyd.svg)](https://hysenlabs.com/projects/tsl0922-ttyd)