Reach your computer's shell from your phone — anywhere, with zero infrastructure.
sshx-mobile turns an sshx.io session into a real, phone-native terminal. A small Android app reshapes the session into a single-window tabbed terminal built for touch, and a host package keeps that session alive and reachable through reboots, network changes and process restarts — so your phone can always find your shell again.
No port forwarding. No VPN. No account. No server to run. The host pushes its current session address to a free, end-to-end-encrypted relay; the app reads it back and reconnects automatically.
Heads up: an sshx session URL grants full read/write access to your shell. Everything after
#is the end-to-end decryption key and never reaches the sshx server. Treat it like a password. See Security.
- Touch-native terminal — one active terminal fills the screen; a top tab bar
switches/creates/closes sessions; a bottom bar sends real
Esc Tab Ctrl Alt - /and arrow keys. Keyboard-aware: the bars lift above the on-screen keyboard. - Swipe to scroll the scrollback; start typing and you snap straight back to the live prompt.
- Lossless persistence — your work lives in a dedicated tmux server, so a dropped connection, a new session URL, or a reboot never loses a pane.
- Auto-reconnect — the app follows the host to its new address with no manual re-pairing.
- Self-healing sessions. If the relay drops the session (long idle, a network blip, waking from sleep), the host detects it and re-mints automatically, so you never land on a stale blank screen.
- Host windows sync to tabs — open or close a window on the host and the app's tabs follow, live.
- Built-in chat & presence — sshx's collaboration, reworked for mobile, with an unread badge.
- QR pairing — scan once; the app remembers the resolver across session changes.
- Tunable density — pick your column count (default 80) for bigger or smaller text.
your phone a free relay your machine
┌───────────────┐ reads addr ┌─────────────┐ pushes addr ┌──────────────────────┐
│ sshx-mobile │ ◀───────────── │ resolver │ ◀───────────── │ sshx ──▶ tmux (-L │
│ (the app) │ │ (MantleDB / │ │ sshx) │
│ │ ──────────────▶│ self-host) │ │ persistent, isolated │
└───────────────┘ sshx session └─────────────┘ └──────────────────────┘
│ ▲
└────────────── end-to-end encrypted sshx session ───────────────────┘
The host runs sshx against a persistent tmux server and publishes the current
(rotating) session URL to a resolver — by default a random, private
MantleDB namespace (no account, no email), or your own
loopback/LAN service. The app polls the resolver, connects to the session, and
re-syncs whenever the host's address changes.
You need two things: the app on your phone, and the host tooling on the machine whose shell you want to reach.
Using Claude Code (or another coding agent)? It can do the whole host setup for you, interactively — package,
sshxCLI, always-on services, resolver + claim, verification, and wiringclaudeinto the app's tmux. See Assisted setup with Claude Code (or run the bundled/sshx-mobile-installskill from a clone). You still install the APK and scan one QR.
Download the latest sshx-mobile-*.apk from the
Releases page and
install it (you may need to allow "install unknown apps" for your browser/file
manager).
You only sideload once: the app updates itself. On launch it checks the Releases feed and, when a newer version exists, offers to download and install it in-app (no Play Store, no re-sideloading) — see Updating.
tmux, python3 and qrencode install automatically as package dependencies. The only extra
piece is the sshx CLI — it isn't in distro repos, so sshx-host-setup offers
to install it for you (or run curl -sSf https://sshx.io/get | sh, which puts it in /usr/local/bin).
Debian / Ubuntu (apt)
curl -fsSL https://moukrea.github.io/apt-repo/pubkey.gpg | sudo gpg --dearmor -o /usr/share/keyrings/moukrea.gpg
echo "deb [signed-by=/usr/share/keyrings/moukrea.gpg] https://moukrea.github.io/apt-repo stable main" | sudo tee /etc/apt/sources.list.d/moukrea.list
sudo apt update && sudo apt install sshx-mobile-hostFedora / RHEL (dnf)
sudo tee /etc/yum.repos.d/moukrea.repo > /dev/null <<'EOF'
[moukrea]
name=moukrea
baseurl=https://moukrea.github.io/rpm-repo/
enabled=1
gpgcheck=1
gpgkey=https://moukrea.github.io/rpm-repo/pubkey.gpg
EOF
sudo dnf install sshx-mobile-hostmacOS / Linux (Homebrew)
brew install moukrea/tap/sshx-mobile-hostArch Linux
# Grab the PKGBUILD from the matching release and build it:
curl -fsSLO https://github.com/moukrea/sshx-mobile/releases/latest/download/PKGBUILD
makepkg -siFrom source
git clone https://github.com/moukrea/sshx-mobile.git
cd sshx-mobile
host/install.shOn the host, run the one-shot setup (as your normal user, not root):
sshx-host-setupIt generates a private resolver, enables the per-user services, and prints a QR code. Open the app, tap Scan QR, point it at the code — done. Your shell is now in your pocket, and it will stay reachable across reboots and network changes.
To check status later: systemctl --user status sshx tmux-server sshx-tmux-bridge.
The app updates itself. On launch (from the home screen, so it never interrupts a live session) it checks the GitHub Releases feed; if a newer version is out it shows an "Update available" dialog. Tap Update and it downloads that release's APK and hands it to the system installer — you grant "install unknown apps" to the app once, the first time. Later re-prompts next launch; Skip silences that one version. (An update can only self-install if it is signed with the same key as the installed app, which holds for these releases; a key change would need a one-time manual reinstall.)
The host you upgrade the way you installed it, then re-run setup so the new version is the one actually running:
sudo apt update && sudo apt install --only-upgrade sshx-mobile-host # or: sudo dnf upgrade sshx-mobile-host / brew upgrade
sshx-host-setupsshx-host-setup restarts sshx and the window bridge (lossless: your tmux
session is preserved, the phone re-resolves automatically) so the update takes
effect right away.
| Gesture / control | Action |
|---|---|
| Tap a tab | Switch terminal |
| Burger menu (top-right) | Columns (text size), new terminal, connected people, chat |
× on a tab |
Close that terminal |
| Bottom bar | Esc Tab Ctrl Alt - / and arrow keys (real terminal input) |
| Set your name (main menu) | Shown to others in the session, persisted across reconnects |
Scrolling works on both surfaces, including inside full-screen TUIs such as Claude Code (whose output lives in the scrollback):
- Scroll — on the phone, swipe up/down on the terminal; on a laptop/desktop
tmux client (
sshx-desk, or aclaudesession wired through the app's tmux) use the mouse wheel. To resume typing after scrolling up, scroll back to the bottom (or pressq/Enter).
Selecting text on the desktop. The wheel and plain-drag native selection can't both be live at once — that's a tmux limitation, not a bug (tmux either owns the mouse for the wheel, or releases it for selection). The desktop defaults to wheel on, so:
- Hold Shift while you drag to select — the terminal's own native selection (works on every terminal, copies via the terminal itself). This is the standard way to select over any mouse-aware program.
- Or run
sshx-mouse offto switch that session to plain-drag native selection for a while (the wheel then sends arrows untilsshx-mouse on).sshx-mousewith no argument toggles.
The mouse setting is per surface: desktop sessions (
sshx-desk,claudewindows) keepmouse onfor the wheel; phone tabs aremouse off, so a stray touch never drops the pane into tmux copy-mode. The phone keeps scrolling because its swipe is sent as keys, not mouse.
Selecting text on the phone is limited today: sshx renders the terminal with WebGL and keeps its terminal object private, and sshx itself has no clipboard support (sshx#77), so the app can't yet pull selected text into the Android clipboard. Tracked for a follow-up (a tmux-capture bridge). For now, copy from the desktop side.
Co-access from your desktop at the same time with sshx-desk (a normal tmux
client sharing the windows). Route claude (or any command) into the shared
session with the installed claude-tmux.sh helper so it shows up as a tab — the
host installer wires this into ~/.bashrc automatically; for zsh add the
delegating wrapper from
docs/setup-with-claude-code.md.
App shows a blank/black screen with no terminals after scanning the QR. The sshx session the resolver points at has gone stale (sshx sessions expire after a long idle or a network drop, and the host process doesn't always notice). The host now detects this and heals itself: a liveness monitor re-mints the session automatically, usually within a minute, and the app re-resolves and reconnects with no re-scan needed. Just wait a moment. To force it immediately:
systemctl --user restart sshx.serviceThis self-heal is on by default; tune or disable it with SSHX_HEALTH_* (see
host/README.md).
Periodic rotation is now optional (it only limits how long any one URL is exposed):
systemctl --user enable --now sshx-rotate.timer # default 1h; `systemctl --user edit sshx-rotate.timer` to changeCheck the host services are up:
systemctl --user status tmux-server sshx sshx-tmux-bridgeNew host windows don't appear as tabs / a closed tab lingers. The window list is
published by sshx-tmux-bridge; make sure it's active. More operational recipes
(reboot, no URL served, sizing) are in docs/runbook.md.
- The session URL's
#fragmentis the end-to-end encryption key. sshx uses it only in the browser; it never reaches the sshx server. Anyone with the full URL can use your terminal. sshx-qrencodes the URL offline — it never sends it anywhere. Never paste an sshx URL into an online QR generator.- The resolver stores the full URL (incl. the key). The default MantleDB namespace
is random and private (the namespace itself is a bearer secret). Claim it
(
sshx-resolver-setup --claim, no email) to attach a key: as of 2026-06 a claimed namespace returns HTTP 401 without the key on reads and writes (verified — seedocs/setup-with-claude-code.md), so claiming is what makes a readable namespace safe. For guaranteed read-privacy with zero third-party trust, run the bundled self-hosted resolver over a network you control. Never expose a resolver to the public internet. (Note: MantleDB lowercases namespaces on claim — use the lowercase form.) host/resolver.env(your live resolver config) is git-ignored and never published. Onlyresolver.env.exampleships.
App: cd android && ./gradlew assembleRelease → app/build/outputs/apk/release/.
Requires JDK 17 and the Android SDK. See android/BUILD.md.
Host packages: SSHX_VERSION=x.y.z nfpm pkg -p deb -f packaging/nfpm.yaml -t dist/
(and -p rpm). CI builds and publishes them on every x.y.z tag — see
.github/workflows/.
The lossless remote-access design, decisions and runbook live in
docs/: setup-host,
setup-app, runbook, and the
ADRs. Host tooling reference: host/README.md.
App internals: android/README.md.
MIT © moukrea