Every time I needed to check on a server from my phone, I went through the same ritual.
Open Termius. Find the saved connection. Enter credentials. Get into the shell. Then the fun begins: I need to cancel a running command — where's Ctrl+C? I need to exit vim — where's ESC? I need to tab-complete a long path — where's TAB?
Termius handles all of this, but I was paying $14/month for what amounts to a keyboard with special keys. And it only works over SSH — if my server doesn't expose an SSH daemon to the internet, I'm stuck.
So I built NomadTTY — a self-hosted web terminal that runs in any browser, with a touch-native keyboard toolbar baked in. No app. No subscription. No SSH daemon on the public internet.
What It Is
Three components, nothing extra:
- ttyd — exposes a bash/tmux session over WebSocket
- nginx — proxies ttyd and injects a keyboard toolbar before the page loads
- tmux — keeps your session alive when you close the browser tab
The toolbar lives at the top of the browser and gives you the keys your phone keyboard is missing:
ESC · TAB · CTRL · SHFT · ALT · ↑↓←→ · HOME · END · PGUP/PGDN · F1–F12
Modifiers are sticky: tap CTRL, then type a letter on your phone keyboard, and it sends Ctrl+C (or whatever letter). No holding. No gymnastics. This is how Termius does it — I just wanted the same thing in a browser tab.
Try It in 30 Seconds
docker run -d -p 80:80 --name nomadtty ghcr.io/shifulegend/nomadtty:latest
Open http://localhost in your mobile browser. The toolbar appears at the top. The terminal fills the rest of the screen.
How the Toolbar Gets Injected (The Neat Part)
nginx has a sub_filter directive that can replace strings in proxied HTTP responses. NomadTTY uses this to inject three things into ttyd's HTML before the page reaches the browser:
- A mobile viewport meta tag
- A 270-byte inline script that hooks
window.WebSocketto capture the/wsconnection aswindow._S -
<script src="/kb.js" defer></script>— the 9 KB keyboard toolbar
The toolbar sends keystrokes by calling window._S.send('0' + bytes) — the same protocol ttyd's own frontend uses. No fork. No modification to ttyd's source. No rebuild.
Phone browser → nginx :80
├── GET /kb.js → keyboard toolbar (9 KB, zero deps)
├── GET /ws → ttyd WebSocket passthrough
└── GET / → ttyd HTML + injected toolbar + viewport meta
↓
tmux new-session -A -s main
↓
persistent bash (survives browser close)
Installing on a Real Server
# One-command install (Debian/Ubuntu)
curl -fsSL https://raw.githubusercontent.com/shifulegend/nomadtty/main/install.sh | sudo bash
# With a custom domain
NOMADTTY_HOST=terminal.example.com sudo -E bash -c \
'curl -fsSL https://raw.githubusercontent.com/shifulegend/nomadtty/main/install.sh | bash'
Works great over Tailscale — expose NomadTTY only on your private network and reach it from anywhere your phone can hit Tailscale. No public SSH port required.
Keyboard at a Glance
| Key | Sends |
|---|---|
| CTRL | Sticky — tap, then type a letter (e.g. Ctrl+C) |
| SHFT | Sticky shift modifier |
| ALT | Sticky — ESC prefix for readline shortcuts (Alt+B, Alt+F) |
| ESC | \x1b |
| TAB / ⇑TAB |
\t / Shift+Tab |
| ↑↓←→ | Arrow keys (modifier combos supported) |
| HOME / END |
\x1b[H / \x1b[F
|
| PGUP / PGDN |
\x1b[5~ / \x1b[6~
|
| Fn | Toggle F1–F12 row |
| A− / A+ | Zoom terminal font in/out |
What It Won't Do
Being honest about the trade-offs:
- Not an SSH client — NomadTTY is a web terminal, not a tunnel or port-forward tool
- One server at a time — no multi-host UI; one NomadTTY instance per server
- No key vault — auth is on you (Tailscale, nginx basic auth, or VPN)
- Browser-only — needs a network connection; no offline mode
If you need Termius-style multi-server management or SSH tunneling: keep Termius. If you have one server you care about and want to reach it cleanly from your phone or tablet without installing anything — this does it.
The Stack
- ttyd — terminal-over-WebSocket, unmodified
-
nginx — toolbar injection via
sub_filter; static serving ofkb.js - tmux — session persistence
-
src/kb.js— 9 KB IIFE, zero dependencies, no build step
No Node.js. No npm. No bundler. Docker images for linux/amd64 and linux/arm64. MIT licensed.
Get It
→ github.com/shifulegend/nomadtty
What do you currently use for terminal access from your phone? Termius? Blink Shell? Raw SSH apps? Something else entirely? I'm curious what the community's solutions look like — and what pain points I may have missed. Drop it in the comments.
Top comments (1)
The sticky Ctrl and F-key emulation in a