WeTTY: a browser terminal over HTTP and HTTPS, and what it costs you
Terminal in browser over http/https. (Ajaxterm/Anyterm alternative, but much better)
At a glance
- What is it?
- WeTTY puts an xterm.js terminal in a web page and pipes it to ssh or /bin/login over websockets. It is small, MIT licensed, and easy to expose by accident, so the flags matter more than the install.
- Who is it for?
- Adopt WeTTY when you want a terminal reachable from a browser on a host you already control, and when a reverse proxy in front of it is part of your plan. Do not adopt it as a multi-tenant jump host: the README's own flags describe password-less connections and URL-supplied ssh destinations, and there is no user database or per-user authorisation to configure.
- 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 1 day ago.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 6, 2026, and from our analysis. They are not legal advice.
Editorial analysis
What WeTTY replaces, and who ends up running it
WeTTY is a terminal emulator served as a web page. The README frames it as an alternative to ajaxterm and anyterm, and gives the reason directly: it uses xterm.js, described there as a full fledged terminal emulation implementation written entirely in JavaScript, and it carries the session over websockets rather than Ajax, which the README says gives better response time. That is the whole pitch. It is not a bastion host, not a session recorder and not an access-control layer.
The people who end up running it are the ones who already have ssh access to a machine and want that access from a browser: a build box reached from a locked-down laptop, a lab machine on a private network, a container that needs a shell without shipping an ssh client to the user. The typical install is one npm global package on the host, and the typical production shape is WeTTY listening on a loopback or private port with nginx or traefik terminating TLS in front of it. The README recommends exactly that: driving WeTTY behind a reverse proxy for HTTPS security and possibly Let's Encrypt support, naming nginx-proxy and traefik, and pointing at a docker-compose example under the containers directory for traefik.
What it is not: a way to hand terminals to untrusted users. There is no account system in the README, no session store, no audit trail. Authentication is whatever ssh or /bin/login does, and the web layer is a transport.
How the websocket terminal actually connects to a shell
The mechanism is visible in the flag list. WeTTY starts an HTTP server (default port 3000, changed with --port, bound with --host, or attached to a unix socket with --socket). The browser loads a page running xterm.js, opens a websocket back to that server, and the server spawns a process whose standard input and output are bridged to the websocket.
Which process gets spawned depends on how WeTTY was started. The README states that if you run it as root it launches /bin/login, where the user name can be specified, and otherwise it launches ssh and connects by default to localhost. The --force-ssh flag overrides the root behaviour so that ssh is used even when running as root. Remote targets come from --ssh-host, --ssh-port and --ssh-user, and the documentation notes a shortcut URL of the form /ssh/<username> to specify the user before the login prompt appears.
Authentication is delegated. --ssh-auth defaults to password, and the README says you can use publickey,password instead. --ssh-key takes a path to an optional client private key, and the README attaches a warning to it: the connection will be password-less and insecure. --ssh-config passes an alternative ssh configuration file, described as equivalent to the -F option in ssh(1), and --known-hosts sets the known hosts path. There is also --command, which changes what runs in the shell.
Two flags change the trust boundary rather than the transport. --allow-remote-hosts lets WeTTY use the host and port parameters in a URL as the ssh destination, and --allow-remote-command lets it use the command and path parameters in a URL as the command and working directory on the ssh host. Both are off unless you turn them on, and both turn a web page into a request forwarder for ssh connections.
Installing WeTTY and opening a first session
The README lists prerequisites of node >=20, make, python and build-essential, and package.json sets the engine requirement to node >=20.0.0. The global install is one command:
npm -g i wettyAfter that, wetty --help prints the full flag list. To connect to a remote ssh host rather than localhost, pass the host, port and user. The README documents exactly these three options for that purpose:
wetty --ssh-host --ssh-port --ssh-userWith the defaults, the server listens on port 3000. Open http://yourserver:3000 in a browser and the README says you will be prompted to log in, or you can go to http://yourserver:3000/ssh/<username> to set the user before the prompt. If you would rather not install Node at all, the README documents a published image:
docker run --rm -p 3000:3000 wettyoss/wetty --ssh-host=<YOUR-IP>That maps container port 3000 to host port 3000 and opens an ssh session to the host given by YOUR-IP at http://localhost:3000. The repository also carries a docker-compose.yml that wires three services together: wetty on 3000, an nginx container on port 80 that envsubst-renders conf/nginx.template into a default.conf, and a wetty-ssh service built from containers/ssh/Dockerfile. The compose entrypoint runs ssh-keyscan -H wetty-ssh against the known_hosts file before starting, which is how the container avoids the host key prompt. That file is the fastest way to see the intended reverse-proxy topology, even if you do not use it as-is.
The flags that decide whether this is safe to expose
The default posture is narrower than the flag list suggests, and that is worth stating plainly. --allow-iframe defaults to allowing same origin only, so embedding elsewhere is opt-in. --allow-remote-hosts and --allow-remote-command are opt-in too. Left alone, the destination and the command come from the server's own configuration, not from the URL.
Turn those two on and the browser becomes a way to ask the server to ssh somewhere and run something. If the WeTTY port is reachable by anyone who can load the page, they are choosing the destination. The README does not describe an allowlist for hosts, users or commands, and it does not describe rate limiting or per-session logging. The only log control in the flag list is --log-level.
The second sharp edge is --ssh-key. The README's own parenthetical says the connection will be password-less and insecure, which is an unusually direct warning to ship in a help string. If that private key is readable by the WeTTY process, anyone who reaches the terminal inherits it.
The third is the root behaviour. Running as root selects /bin/login, which is a local login path rather than an ssh path. --force-ssh exists to get out of that, and the README does not explain why you would want /bin/login in a container, so treat the root case as something to test deliberately rather than something to inherit from a base image.
Finally, the README does not document rollback, session revocation or a way to kill an established websocket from the server side. Once a session is open, closing it is the client's job.
WeTTY versus ttyd, and why the choice is about the backend
The obvious comparison is ttyd, and the difference is not the browser side. Both put a terminal in a web page. The difference is what sits behind it. ttyd's model is to serve a command you name, so the access control question becomes which command runs and as whom. WeTTY's model is to serve ssh (or /bin/login when running as root), with ssh handling authentication, host keys and known-hosts checking, and with the ssh config file passed through via --ssh-config.
That means WeTTY inherits ssh's machinery: public key auth, agent forwarding if your config does it, known hosts verification, and the ability to target a machine that is not the one running WeTTY. It also means WeTTY inherits ssh's failure modes, including host key prompts, and the repository's compose entrypoint exists precisely to pre-seed known_hosts with ssh-keyscan so the container does not stall on one.
If your target is a single local shell and you want the smallest possible surface, a command-serving tool is a better fit. If your target is a fleet of machines you already reach over ssh, WeTTY's flags map onto that directly and you do not have to build an ssh wrapper yourself. Neither choice removes the need for a proxy in front: the README recommends one for WeTTY, and the docker-compose.yml shows nginx doing it with a template file rather than a hand-written config.
Maintenance, release cadence and the MIT licence
The repository is not archived. The last push was on 2026-09-21, and the most recent release listed is v3.2.2 on 2026-09-21, preceded by v3.2.1 on 2026-09-09 and v3.2.0 on 2026-07-16. That is a steady trickle of releases rather than a burst, and it means the version you install today is close to the tip of main.
Upgrade cost is low but not zero. The package ships build/ and conf/ only, per the files field, and the bin entry points at ./build/main.js. The runtime requirement is Node 20 or newer, which is the constraint most likely to force an upgrade: a host pinned to an older Node cannot run a current WeTTY. The dependency list is ordinary and mostly front-end (express, compression, etag, the @xterm packages), so a global install pulls a normal node_modules tree rather than a native toolchain. The prerequisites list make, python and build-essential, which suggests native compilation somewhere in the install path, so build images need those present even if the runtime image does not.
The licence is MIT, declared in package.json and in the LICENSE file at the repository root. MIT is permissive: it allows commercial use and modification, and it requires that the copyright notice and permission notice be preserved in copies. That is the extent of what can be said here; whether your distribution model satisfies it is a question for your own legal review, not something a README settles. Note also that the npm package is published with provenance enabled in publishConfig, so the published artifact is tied to a build from the repository.
What to check before you point a browser at it
Start with wetty --help on the exact version you installed, because the flag list is the whole configuration surface and it is short enough to read in one screen. Then decide three things explicitly. Is the URL allowed to choose the ssh destination? If not, leave --allow-remote-hosts off. Is the URL allowed to choose the command and working directory? If not, leave --allow-remote-command off. Is the page allowed to be embedded by another origin? If not, leave --allow-iframe at its default, which the README says is same origin.
Then decide how the port is reached. The README's recommendation is a reverse proxy with HTTPS, and the repository's docker-compose.yml shows the nginx pattern with conf/nginx.template and the NGINX_DOMAIN, NGINX_PORT, WETTY_HOST and WETTY_PORT environment variables. If you are not using a proxy, at minimum bind with --host so the listener is not on every interface.
Finally, check the auth path you are actually using. If --ssh-key is set, the README's warning applies and the session is password-less. If you are running as root, the process launched is /bin/login, not ssh, unless you pass --force-ssh. Those two facts determine what a visitor who reaches the port can do, and neither is something the package decides for you.
Editorial conclusion
Adopt WeTTY when you want a terminal reachable from a browser on a host you already control, and when a reverse proxy in front of it is part of your plan. Do not adopt it as a multi-tenant jump host: the README's own flags describe password-less connections and URL-supplied ssh destinations, and there is no user database or per-user authorisation to configure. Before exposing it, decide whether the URL is allowed to name the ssh destination (--allow-remote-hosts), the command and working directory (--allow-remote-command), and whether embedding is permitted (--allow-iframe), then verify with wetty --help on the exact version you installed. The npm package is published with provenance and the project is MIT licensed, so the code is auditable, but the security posture is the deployment's, not the package's.
Frequently asked questions
How do I install WeTTY?
The README gives a single global npm install, npm -g i wetty, with node >=20, make, python and build-essential listed as prerequisites. A container image is also published, and the README documents running it with docker run --rm -p 3000:3000 wettyoss/wetty --ssh-host=<YOUR-IP>.
What is WeTTY?
WeTTY is a terminal served in a browser over HTTP or HTTPS. The README describes it as an alternative to ajaxterm and anyterm that uses xterm.js for terminal emulation and websockets rather than Ajax, which it says gives better response time.
What is the difference between WeTTY and ttyd?
Both present a terminal in a browser, but WeTTY's backend is ssh by default, or /bin/login when it runs as root, with --ssh-config passing through to ssh(1). That means ssh handles authentication and host key checking, and the repository's compose entrypoint runs ssh-keyscan to pre-seed known_hosts so the container does not stall on a prompt.
What can I use instead of WeTTY?
The README positions WeTTY against ajaxterm and anyterm, and states its advantages as xterm.js terminal emulation and websockets instead of Ajax. If you want the smallest possible surface for a single local shell, a tool that serves a command you name avoids the ssh layer entirely.
Official sources
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.
[](https://hysenlabs.com/projects/butlerx-wetty)