1. Overview
meshTerm is an SSH client for iOS and iPadOS. It connects to remote servers over standard SSH, with built-in Tailscale networking, SFTP, port forwarding, and a fast Metal terminal.
For sessions that outlive a disconnect, meshTerm offers three levels of persistence, chosen per host: None (a plain SSH shell that ends with the connection), tmux (your shell survives on the server using software already on most machines), and Halo - the optional open-source mtRoam daemon on your own host, for full persistence that survives network changes, app suspension, device switches, and daemon restarts. See Roam for the walkthrough and Halo for how the persistent ring buffer works.
Beyond the terminal, meshTerm includes:
- Multiple concurrent sessions per host, with attach modes for sharing or watching.
- A predictive-echo overlay that makes typing feel native on slow links.
- A browseable view of the host's shell history, plus a chip strip of inline suggestions.
- An SFTP file browser that integrates with the iOS Files app.
- A customisable macro bar and a searchable Snippet Vault.
- Native Tailscale integration for keyless SSH on your Tailnet.
2. What's new
meshTerm is currently at version 2.1.5. The headline additions since 2.1.2:
2.1.5
- Faster, steadier reconnects - returning to the app is quicker and more reliable. meshTerm refreshes stale network paths the moment you wake, re-probes a dropped link in about a second, and no longer double-reconnects on a deep link. Your sessions snap back where you left them.
- Clean reattach for full-screen tools - Claude Code, vim, htop, and other full-screen apps now repaint correctly when you return to a session. No more blank screen, split screen, or missing prompt after backgrounding, locking, or switching devices.
- Secret Profiles, delivered live - inject a bundle of environment variables and keys straight into the session you are already in, with one tap. Nothing is typed and nothing is left on screen. It works over plain SSH, MTRoam, and tmux. See Secret Profiles.
2.1.4
- Agent mode - run Claude Code, Codex, and Gemini on your own hosts, driven from a native in-app chat: streaming replies, per-tool approvals that stay pinned until you answer, plan mode, model and thinking-effort pickers, token-usage chips, slash commands, and file attachments from iOS. Agent sessions survive app restarts and network drops, and you can resume any previous conversation. See Agent Mode.
- Approvals and sandboxing - Codex and Claude ask before commands and edits, and the request waits for you instead of scrolling away. Gemini's Sandbox mode pauses writes outside the working directory for your approval, with optional bubblewrap kernel hardening on the host.
- The keyboard palette - the accessory keyboard is now a three-page palette: a features page (files, buffer and history search, snippets, tunnels, agents), a keys page built from your own key groups, and a sessions page for one-tap switching. See Keyboard.
- One-tap tunnels - add a port forward straight from the Tunnels sheet on a running session. It starts immediately with no reconnect and persists on the host for every future connect.
2.0 to 2.1.2
- All-new Metal terminal - the renderer was rebuilt from scratch for smoother output and rock-steady layout. Live content stays pinned above the keyboard and the grid holds steady through rotations, with nothing to configure.
- iCloud Sync (Pro) - hosts, known-host trust, your SSH key list, macros, snippets, Tailscale profiles, session tags and preferences sync across your devices. Secrets ride iCloud Keychain (end-to-end encrypted). Opt-in, off by default.
- Port forwarding / Tunnels - local (
-L) forwards and a dynamic SOCKS5 (-D) proxy, configured per host, with an in-app browser for local web forwards. - Session takeover - open a session on one device and take it over from another with a single tap; the displaced device drops to read-only with a Take Over banner.
- Sessions tab - the old Hosts tab is retired; your hosts and their live and resumable sessions now live together under Sessions, with per-host CPU, RAM and storage stats.
- Tailscale, deeper - browser sign-in (no auth keys to paste), multiple named profiles with per-host binding, an in-app device browser, and fully in-process networking (no Tailscale app required). Headscale and self-hosted control servers are supported.
- Retro color modes - Green, Blue and Red phosphor plus Monochrome, alongside True Color, with diff-aware brightness so additions stay bright and removals dim.
- Agent-aware terminal - a return-to-prompt pill for Claude Code and Codex, a "Claude may be idle" banner with one-tap Save & Restart, and a fullscreen mode for agent TUIs.
- Reconnect hardening - faster, quieter resumes after a network change or a long spell in the background, with anti-flood backoff.
- In-place host-key re-trust and SSH key file import.
3. Getting started
Adding your first host
- Open meshTerm. On first launch the Sessions tab is empty ("No Hosts").
- Tap + in the top right (or the globe icon to browse and add a host from your Tailnet).
- Fill in the connection details:
- Label - a friendly name (e.g.
demo). - Hostname or IP - the address of your server.
- Port - defaults to 22.
- Username - your account on the server.
- Label - a friendly name (e.g.
- Pick an authentication method - password, SSH key, or (if the server runs Tailscale SSH) Tailscale.
- Optionally pick a Connection method - plain SSH or Roam. You can toggle this later from Edit Host.
- Tap Save.
Connecting
Tap any host in the list to open a terminal session. On first connect to a new host, meshTerm asks you to verify its host-key fingerprint (see Known Hosts) - tap Trust & Connect to proceed.
4. Managing hosts
Editing a host
Long-press a host card or open its detail screen, then tap Edit. The editor groups settings into Connection, Authentication, Protocol (plain SSH or Roam), Session, TUI Detection, Tags, Operating System, and Environment Variables.
Deleting a host
Tap Edit at the top of the Sessions tab, then the red minus on a host to remove it (you can also drag to reorder here). A host is also reachable by long-press for a context menu of actions.
Tags & filter chips
Tag hosts in the editor's Tags section (comma-separated, e.g. prod, web). A chip row at the top of the Sessions tab lets you filter by tag.
Host import / export
Settings → Hosts gives you two buttons:
- Import from .ssh/config - pick a host you can already SSH to. meshTerm reads its
~/.ssh/configover SSH and shows a checklist of entries to mirror. Imported hosts start in password mode with an empty password - set credentials in the editor before connecting. - Export to ssh_config - emits a standard OpenSSH config file you can drop into your dotfiles or AirDrop to a laptop. Roam-enabled hosts are exported as plain SSH (the Roam protocol is meshTerm-specific).
Distro / OS icons
Each host card shows a small icon for its operating system - Ubuntu, Debian, Fedora, CentOS / Rocky / Alma, Arch, openSUSE, NixOS, Linux Mint, Gentoo, elementary OS, Void, Solus, Zorin, deepin, macOS (Apple logo), FreeBSD, Android, AIX, and GNU/Hurd. The icon is tinted with the host's per-card hue. Choose the OS in Edit Host → Operating System; Unknown falls back to the generic Linux icon and shows the host's initials as a monogram.
5. Connecting to a host
Tap any host in the Sessions tab to open a terminal session.
Verifying the host key
On the first connection to a new host, meshTerm shows its host-key fingerprint and asks you to confirm it (trust on first use). Tap Trust & Connect to save the key and proceed. If a host's key changes later, meshTerm does not fail silently: it shows an accept-or-compare sheet so you can review and re-trust the new key in place.
Reconnecting
meshTerm keeps working as you move between networks and background the app; when you return, live sessions reconnect automatically. A plain SSH shell ends when its connection drops. To keep a shell alive across drops, use tmux or Halo (see Roam).
6. Roam: persistent sessions
Roam is meshTerm's persistent-session mode, powered by the open-source mtRoam daemon (mtroamd) on your own host. Turn it on per host and your shell survives reconnects, app suspension, device switches, and even daemon restarts, with full scrollback replayed on reattach. Its signature is Halo, the persistent ring buffer described below.
Installing the daemon on a host
The first time you tap Connect on a Roam-enabled host that doesn't already have the daemon, a Set Up Roam sheet appears. The sheet runs a six-stage install pipeline:
- Probing - meshTerm runs a few read-only commands over SSH to detect the platform (Linux distro family, macOS, BSD), the architecture, and where to install the binary.
- Downloading - fetches the signed daemon binary and the companion mtroam CLI from the project's GitHub Releases. Progress is shown as a single bar covering both files.
- Verifying - verifies the release's minisign signature against keys built into the app, then checks each binary's SHA-256 against the signed checksum file. If anything fails, the install aborts with a clear error and the binaries are never uploaded.
- Uploading - SFTPs the verified binaries into
~/.local/bin/on your host and marks them executable. - Installing service - sets up the right supervisor for the platform:
- Linux with systemd → a user-level systemd unit, optionally with linger enabled so the daemon survives a logout.
- macOS → a per-user launchd agent.
- Hosts where neither is available → a
nohup-supervised fallback. The sheet tells you the system is on the fallback path.
mtroamdandmtroamfrom any SSH session. - Smoke test - a four-check verification: the binary runs, the daemon responds, your login shell can find it on PATH, and the mtroam CLI is in place. Each check is shown as a row in the sheet. If a check fails, you get an inline Retry Checks button (cheap re-run) and a Full Reinstall button (escalation).
On success, the sheet shows the supervisor that was set up. On Linux hosts, you'll also see a distro-tailored firewall instruction for the UDP port the daemon uses - copy the one line and run it in a shell to open the port for inbound connections.
Updating the daemon
When meshTerm needs a newer daemon than the one on the host, the host's editor shows a Daemon update available banner with the installed and available versions. Tap Update now and the sheet drives a self-update - the daemon swaps itself out via atomic rename so the running process isn't interrupted, then restarts via its supervisor.
Reinstalling or recovering
Open the host's detail screen for a Reinstall button. Reinstall stops the running daemon, removes the binary, re-runs the full install pipeline, and brings the supervisor back. Your pairing state and cert are preserved, so reinstall doesn't require re-trusting the host.
If the install fails
Each stage has its own failure message and a copyable manual command you can paste into a shell to run the same step yourself. Common causes:
- Probing failed - the SSH connection itself isn't working, or the user account doesn't have a writable home directory.
- Verification failed - extremely rare; means the downloaded files didn't match the signed checksum. Re-run; if it persists, file a report.
- Service install failed - most often systemd-user can't talk to its bus because the user isn't lingering. The sheet surfaces the exact next step.
Choosing a transport (Direct vs SSH Tunnel)
Roam can reach the daemon two ways. Set this in Edit Host → Protocol & Transport: pick Protocol → mtRoam, then open Transport Details → mtRoam Transport:
- Direct (QUIC/TCP) - meshTerm dials the daemon's QUIC port directly over UDP. Lowest latency, but the daemon's UDP port (49820 by default) has to be reachable from your device. The install sheet shows the exact firewall line to open it.
- SSH Tunnel - the Roam protocol is carried inside the SSH connection you already authenticated with. Nothing extra to open: it works through any firewall that allows SSH, at a little more latency than Direct.
If you choose Direct and the UDP port turns out to be blocked, meshTerm automatically falls back to the SSH tunnel for that connection, so a session always comes up. Over Tailscale, Roam routes through the encrypted tunnel regardless of this setting.
Multiple named sessions per host
Each Roam host can hold any number of named sessions: dev, logs, tests, whatever you want. Tap the host and a picker appears showing the existing sessions plus a + New entry. Tap a session to attach; tap + New to start a fresh one.
Manage sessions from the picker:
- Rename - long-press a row.
- Kill - swipe a row left and tap Kill. Confirmation required.
If a host only has one session, meshTerm skips the picker and attaches directly. If it has none, a fresh session is spawned and you connect to it.
Halo: the persistent ring buffer
The thing that makes Roam durable is a per-session ring buffer the daemon keeps for every session. We call it Halo. It is a rolling window of your session's output (up to 4 MiB per session) that lives on the host, independent of whether any client is attached. When your phone drops off, backgrounds, or switches networks, the shell keeps running and its output keeps flowing into the ring, so nothing is lost while you are away.
When you reattach, the daemon replays the ring from the exact byte you last acknowledged and then hands you straight into the live stream with no visible seam. Because the buffer tracks output by byte position, replay resumes precisely where you left off rather than redrawing the whole screen. A few details worth knowing:
- Recency window. meshTerm asks for only as much replay as your scrollback setting can actually display, so a quick reconnect does not re-stream megabytes you will never scroll back to.
- Full-screen apps. Inside a full-screen TUI (vim, htop, a pager) the replay budget tightens automatically, since an alt-screen app has no scrollback of its own. The daemon also reconstructs the app's stable bottom rows (status bars, prompts) so they are intact the moment you reattach.
- Honest truncation. If you were away long enough that the oldest output rolled out of the 4 MiB ring, meshTerm tells you plainly that some output was lost during the disconnect rather than pretending the scrollback is complete.
- Survives daemon restarts. With Persist across daemon restarts on, the ring is checkpointed to disk, so your scrollback and session come back even after a self-update or a host reboot.
Halo lives entirely on the open-source daemon; nothing about your session travels anywhere but between your device and your own host. The full buffer and replay design is documented in the mtroamd repository.
Default session and pinning
In Edit Host → Roam Persistence:
- Default Session - pick which named session the host opens by default. None uses your most recently attached one.
- Always open this session - pins the host directly to the default session, skipping the picker. The picker is still available via long-press → Sessions….
- Persist across daemon restarts - when on, the session's scrollback is snapshotted to disk so it survives if the daemon restarts (e.g. after a self-update or host reboot).
Keep-alive (idle timeout)
Pick how long the daemon should hold a Roam session when no client is attached. Options: Default (uses the daemon's built-in timeout), 1 hour, 1 day, 7 days, 30 days, or Never (kept until the daemon restarts or you kill it). New hosts inherit whatever you choose under Settings, Persistence.
Attach modes
When you join a Roam session, meshTerm uses one of three roles:
- Exclusive (default) - you receive output, you send keystrokes, you own the terminal size. If someone else attaches exclusively, you're handed off to read-only.
- Read-only - you see everything but your keystrokes are dropped at the daemon. Useful for watching a colleague drive, or for a second device tailing along.
- Passive - a quiet tail. Same as read-only but invisible to the other clients' "who else is attached" indicators. Used by the
mtroam tailcommand.
Any number of read-only watchers can coexist with a single exclusive driver. The session label in the bar shows your role.
Desktop CLI: mtroam 2.0
The Roam daemon ships with a small companion command-line tool called mtroam, installed on your host as part of Set Up Roam. Use it from any SSH session on the host (or from a laptop with ssh user@host mtroam …) to manage sessions without opening the iOS app.
Subcommands:
mtroam list- enumerate sessions on this host (id, name, idle time, attached clients).mtroam session-info <name>- detail for one session, including geometry and idle state.mtroam status- daemon health snapshot.mtroam search <regex> <session>- grep a session's scrollback.mtroam doctor- diagnostic checks for daemon, supervisor unit, and lingering.mtroam tail <session>- passively attach to live output. No input, no replay; just a tail.mtroam new --name <name>- create a new named session without attaching to it. Useful for pre-creating a session before opening the app, or for cron jobs.mtroam attach <session>- attach to a session as the local terminal. Detach with~.on a fresh line.mtroam kill <session>- reap a session.mtroam rename <session> <new-name>- rename a session. Buffer and active attaches are unaffected.mtroam update- check for and apply a signed self-update from the project's GitHub Releases.mtroam restart- cycle the daemon via its supervisor. In-flight sessions survive the restart.mtroam uninstall- remove the mtroam binary.
Authentication uses your normal SSH config - ~/.ssh/config, ssh-agent and keys all work. mtroam invokes the system ssh binary, so anything that works for ssh user@host works here too.
--host user@host to pick a target, or reads $MTROAM_HOST, or falls back to ~/.config/mtroam/host.
7. Agent Mode 2.1
Agent Mode runs a coding agent on one of your own hosts and gives you a native chat to drive it from. Start Claude Code, Codex, or Gemini on any host, and instead of watching a raw TUI scroll past, you get a real conversation: streaming replies, tappable approvals, and the agent's tools, all inside meshTerm's own interface.
The agent runs where your code is. meshTerm launches the provider's own CLI on the server, over the same SSH or mtRoam connection you already use, so the model works against your real files, your real environment, and your own API keys or logins on the host. Nothing about your project leaves your host and your device.
Starting an agent
Open the keyboard palette's features page and tap Agents, or start one from a running session. Pick a provider and meshTerm opens the agent chat. From there you type as you would in any chat: your message goes to the agent, and its reply streams back token by token.
Replies, models, and effort
- Streaming replies - text appears as the model produces it, so you can read along and stop early if it is heading the wrong way.
- Model picker - choose which model the provider should use for this conversation.
- Thinking-effort picker - dial how much reasoning the model spends before it answers, where the provider supports it.
- Token-usage chips - each turn shows what it cost in tokens (or an estimate where the provider does not report exact counts), so a long session never surprises you.
- Slash commands - the provider's own slash commands work straight from the chat input.
Plan mode
Turn on plan mode and the agent works out an approach and lays out the steps before it touches anything. Review the plan, then let it proceed. It is the safe way to point an agent at a change without it editing files the moment you hit send.
Approvals and sandboxing
Agents ask before they act, and the request waits for you instead of scrolling away:
- Codex and Claude ask before running a command or editing a file. The approval request appears as a pill in the chat and stays pinned until you answer it, so a prompt never gets lost in a fast-moving conversation. Approve or deny with a tap.
- Gemini Sandbox pauses any write outside the working directory and surfaces it for your approval, so the agent can read freely but cannot change files elsewhere without a yes from you. On hosts that support it, an optional bubblewrap layer adds kernel-level hardening around the agent's process.
File attachments
Attach files straight from iOS into the agent's workspace. Pick from the iOS Files sheet and meshTerm places them where the agent can see them, so you can hand the model a log, a screenshot, or a document without leaving the chat.
Persistence and resume
Agent sessions are as durable as the rest of Roam. An agent keeps running through an app restart or a network drop and picks straight back up when you return. You can resume any previous conversation, including agent sessions you started from the terminal rather than the chat, so a session you kicked off in a plain shell is still yours to continue in the native chat later.
8. Authentication
Password
Enter the password when adding or editing the host. Passwords are stored in the iOS Keychain.
SSH key
Pick a saved SSH key from the host editor. The private key never leaves your device - meshTerm signs the authentication challenge locally and sends only the public key to the server. See SSH keys.
Tailscale SSH
For servers on your Tailnet with Tailscale SSH enabled, no password or key is required. Tailscale authenticates the connection at the network layer using your device's identity. See the Tailscale Setup Guide.
9. SSH keys
Manage keys in Settings → SSH Keys.
Supported key types
- Ed25519 (recommended default).
- ECDSA on NIST curves P-256, P-384, P-521.
RSA keys aren't supported in 2.0; the importer will tell you if you paste one.
Generating a new key
- Settings → SSH Keys → Add Key.
- Pick a type and enter a label (e.g. "Work iPhone").
- Tap Generate. meshTerm creates the key pair, saves the private half in the iOS Keychain, and shows the public key in OpenSSH format.
- If you have saved hosts, an Install on a Host section appears immediately. Pick a host, tap Install Key, and meshTerm appends the public key to
~/.ssh/authorized_keyson that server.
Importing an existing key
- Settings → SSH Keys → Add Key → Import.
- Paste the full OpenSSH-format private key (including the BEGIN and END lines).
- If the key is passphrase-protected, enter the passphrase. meshTerm decrypts it on import and stores the seed in the Keychain - there's no second prompt at connect time.
- Tap Import.
Copying a public key
Tap the copy icon next to a key to copy the OpenSSH public key to your clipboard. Paste it into ~/.ssh/authorized_keys on any server you want passwordless access to.
Deleting a key
Swipe a key left and tap Delete. The private key is permanently removed from the Keychain. Hosts using that key will fail to authenticate until you reassign one.
Where keys live
- Private keys are stored in the iOS Keychain. By default they are marked
WhenUnlockedThisDeviceOnly, so they stay on this device and never reach iCloud. If you turn on iCloud Sync (Settings; off by default; a Pro feature), your secrets migrate to iCloud Keychain, which is end-to-end encrypted, so they become available on your other devices. Tailscale credentials never sync. - Public keys are stored alongside the host record for display purposes.
10. Tailscale
meshTerm has a built-in Tailscale networking stack, so no separate Tailscale app is required. Add your Tailnet credentials and meshTerm connects to your devices directly over WireGuard. SSH, SFTP, Roam, and all auxiliary connections route through the encrypted Tailscale tunnel natively.
Routing modes
Each host uses one of two routing modes. Set the default in Settings → Connections → Tailscale, and override it per host in Edit Host:
- Built-in (Tailscale) - meshTerm's own in-process Tailscale node. Works alongside any system VPN and needs a Tailscale profile. This is the recommended default, and it does not depend on the Tailscale app being installed.
- Device network - uses your device's normal networking: it routes via the Tailscale app if that app is running, or connects directly otherwise.
Setting up Tailscale 2.0.2
- Tap the globe icon in the host list (or go to Settings → Tailscale) and create a profile.
- Tap Sign in with Tailscale - a browser sheet opens and you sign in with your Tailscale account (Google, Microsoft, GitHub, Apple…). No keys to generate, nothing to renew. If your tailnet requires device approval, meshTerm waits and connects the moment an admin approves.
- That's it. Tap the globe icon to browse your Tailnet and add devices in a tap - once connected, the device list comes straight from your tailnet, no API token required.
Prefer not to sign in through a browser? An auth key works exactly as before. And an optional read-only API token supplements discovery: it lets meshTerm list your devices even when it isn't connected to the tailnet (or when you route through the Tailscale app in Device network mode).
The embedded Tailscale node starts automatically when you open the app. You can see its status (connected, IP addresses, signed-in identity) in Settings → Tailscale.
Tailscale SSH
Devices on your Tailnet with Tailscale SSH enabled connect without passwords or keys - Tailscale authenticates the connection at the network layer using your device's identity. Set the host's authentication method to Tailscale SSH in the host editor.
Auth keys (alternative to sign-in)
If you'd rather not authenticate through a browser - or you're automating setup - the embedded node can join your Tailnet with a Tailscale auth key instead. Generate one at login.tailscale.com → Settings → Keys:
- Click Generate auth key.
- Enable Reusable so the key survives app restarts.
- Optionally enable Ephemeral so the node auto-removes from your Tailnet when meshTerm isn't running.
- Copy the key and paste it into Settings → Tailscale → Auth Key in meshTerm.
The auth key is stored in the iOS Keychain. meshTerm uses it to join your Tailnet on each launch - no browser login required. Auth-key profiles browse the device list exactly like signed-in ones: connected = no API token needed.
Multiple Tailscale profiles 2.0
meshTerm can hold more than one Tailscale identity - for example a work tailnet and a personal one, or a separate profile per client. Manage them in Settings → Connections → Tailscale, where each profile carries its own sign-in (or auth key) and optional read-only API token.
- Add a profile - tap New Tailscale Profile, name it, and sign in with your Tailscale account (or paste an auth key).
- Switch profiles - open a profile and tap Connect. Only one profile's node is up at a time, so switching brings the previous one down and the chosen one up. The device browser also offers Connect / Disconnect / switch directly, and warns you first if switching would drop live sessions.
- Browse per tailnet - the device browser shows whichever profile you pick. The connected profile lists devices straight from its tailnet; browsing a profile that isn't connected uses its read-only API token.
Binding a host to a profile
In the host editor you can bind a host to a specific Tailscale profile. When you open that host, meshTerm switches to its bound profile first - so a work host always comes up on the work tailnet and a personal host on the personal one, with no manual switching. Hosts left unbound use whichever profile is the current default.
11. Port forwarding & Tunnels
meshTerm can forward network ports over an SSH connection, the same way ssh -L and ssh -D do on a desktop. Rules are configured per host and managed from the Tunnels screen.
Rule types
- Local (
-L) - binds127.0.0.1:<port>on your device and forwards each connection to ahost:portreachable from the server. Good for reaching a database, an admin panel, or an internal web app that only listens on the server's own network. - Dynamic (
-D) - runs a SOCKS5 proxy on127.0.0.1:<port>; point an app at it and its traffic routes out through the host. Remote (-R) forwarding is not supported yet.
Setting up a rule
In Edit Host → Port Forwarding, tap Add Port Forward, choose Local or Dynamic, and set the local port (and, for Local, the destination as seen from the host). Add a label if you like, then toggle it on.
Managing tunnels
Open Tunnels from a host's long-press menu or the terminal's more-menu. Each rule shows a live status: Listening, Not running, or an error. Tunnels start when the session connects.
For a local web forward, tap its row to open the target in meshTerm's built-in browser; the tunnel stays connected while you browse. For a SOCKS proxy, point an app at 127.0.0.1:<port>.
12. Keyboard 2.1
The on-screen accessory above the keyboard is a three-page palette. Swipe left and right to move between the pages, or tap the page control. Each page keeps a different set of tools within thumb reach.
The three pages
- Features - one-tap access to the things you reach for mid-session: the SFTP file browser, buffer and history search, the Snippet Vault, Tunnels, and Agents.
- Keys - a custom terminal keyboard built from your own key groups: F-keys, navigation, modifiers, and the keys you use most. This is where Ctrl, Esc, Tab, the arrows, and your top row live.
- Sessions - your live and resumable sessions, for one-tap switching without leaving the terminal.
Customize Keyboard
Open Settings → Keyboard → Customize Keyboard to shape the palette to your hands:
- Top row - reorder the always-visible row of your most-used keys, and move keys to or from a drawer.
- Key groups - build the Keys page out of named groups of keys, so related keys sit together.
- Minimized bar - pick which keys appear on the slim bar when the palette is minimized.
Minimizing and dismissing
Minimize the palette to a slim bar at the bottom edge and hand the rest of the screen back to the terminal for reading long output (logs, man pages, build output). Tap the bar to expand it again, or tap a single key on it to send that key.
To put the keyboard away entirely, swipe down on it, or tap the hide button next to Back. Open Sessions Minimised (Settings → Keyboard) starts every session with the palette already minimized.
Left-Handed Chrome
Settings → Keyboard → Left-Handed Chrome mirrors every bottom-edge control to the leading side of the screen, so the palette's chrome sits under your thumb when you drive one-handed from the left.
External keyboards
Connect a Magic Keyboard, Smart Folio, or any Bluetooth keyboard and the on-screen accessory bar hides automatically. Disconnect and it returns. While an external keyboard is attached, the following shortcuts are available:
| Shortcut | Action |
|---|---|
| ⌘ T | New session |
| ⌘ K | Close current session |
| ⌘ 1 - ⌘ 9 | Jump to tab N |
| ⌘ [ / ⌘ ] | Previous / next tab |
| ⌘ F | Open scrollback search |
13. Predictions & history 2.0
Predictive echo overlay
When you're typing at a shell prompt, meshTerm mirrors your keystrokes on screen immediately and then reconciles them with the echo that comes back from the server. On a slow link, typing feels native - you see the letters appear as you type, not after a 200 ms round-trip.
The overlay arms itself when meshTerm sees a shell prompt (recognised by the trailing prompt character: $, #, >, or %) and disarms when an alt-screen TUI like vim, htop, or less takes the screen. On Roam hosts, the daemon also reports the live state of the shell's echo flag so meshTerm knows authoritatively whether predictions are safe.
If a prediction doesn't match what the shell actually echoed, the overlay rolls back automatically - you'll see a brief flicker as the mirrored characters are erased and the real ones drawn.
History browse
Pull up the keyboard's history panel to see the host's recent shell history in a sheet. Search across commands and tap one to send it to the active session - the panel closes and the command runs.
The history is fetched live from the host's own ~/.bash_history or ~/.zsh_history over the SSH connection. Nothing is stored locally; what you see is what the shell actually ran.
Prediction chip strip
As you type, a strip of matching commands from the host's shell history appears above the keyboard. Tap a chip to commit the rest of that command without finishing the keystrokes. The chip strip is purely a fast path on top of the history sheet - it doesn't surface snippets, macros, or anything else.
14. TUI return-to-prompt pill 2.0
When a full-screen terminal app (TUI) like Claude Code or Codex CLI is running, scrolling up to look at earlier output normally means the prompt scrolls off the bottom of the screen. The TUI return-to-prompt pill is a small floating button that surfaces in those situations and jumps you straight back to the live prompt with one tap.
Three styles, picked automatically by what's running:
- Claude Code - brand-orange pill with a white icon. Sends Ctrl+End to jump back to Claude's live prompt.
- Codex CLI - white pill with a black icon. Sends Ctrl+T to toggle the Codex transcript pager.
- Generic TUI - blue pill. Sends Ctrl+End, which works in most alt-screen TUIs and pagers.
Over a Roam (mtRoam) connection the daemon reports the real foreground process running on the host, so detection is exact. For plain SSH hosts, meshTerm falls back to a lightweight heuristic based on the terminal title.
Per-host override
Edit Host → TUI Detection lets you change the behaviour per host:
- Auto - detect from the title (default).
- Claude Code - force the Claude pill on this host even if the title detection fails (some custom zsh prompts overwrite the title).
- Codex CLI - force the Codex pill.
- Off - suppress the pill on this host entirely.
Master kill switch
Settings → Terminal has a master toggle that disables the TUI pill across every host. The pill is on by default.
15. Snippet Vault
The Snippet Vault is a searchable library of terminal commands you can send to the active session in one tap. Open it from the terminal toolbar.
Default snippets
meshTerm ships with two folders of pre-loaded snippets:
- Linux - common admin commands like
ls -lah,df -h,ss -tuln,systemctl status. - OpenClaw - commands for the OpenClaw toolkit.
Using a snippet
Tap any snippet to send the command text to the terminal. The Vault closes automatically.
Adding snippets
- Tap + in the Snippet Vault toolbar.
- Enter a title and the command text.
- Optionally pick a folder.
- Tap Save.
Secret snippets
When adding a snippet, mark it as Secret to back its command text with the iOS Keychain instead of plain storage. Optionally require Face ID, Touch ID, or your device passcode each time the snippet fires - useful for sudo passwords, API tokens, or anything you don't want appearing in a list of commands.
Folders & search
Tap New Folder to organise. The search bar at the top of the Vault searches across titles, command text, and tags.
16. Secret Profiles 2.1
Secret Profiles are a meshTerm Pro feature. Creating and using them requires an active Pro subscription.
A Secret Profile bundles a set of environment variables and keys under one name, so you can hand a whole environment to a session at once. Tap to inject a profile into the session you are already in: its variables and keys reach the running shell invisibly, with nothing typed into the terminal and nothing left on screen afterward. Injection works over plain SSH, mtRoam, and tmux.
When injected values take effect
Injection delivers the values to the shell, which picks them up at its next prompt. That means they apply to the next command you run, not to a command already in progress. Inject only updates a pane sitting at a shell prompt; a full-screen app or an agent in the foreground is skipped.
A running agent has an important limitation: Claude Code, Codex, and Gemini keep the environment they launched with, because a running process's environment cannot be changed from the outside, and injection only lands at a shell prompt. For that reason the Inject button is disabled while an agent is in the foreground. To give an agent new secrets: return to a shell prompt, tap Inject there, then restart or resume the agent session, which relaunches it with the new values already in place so it reads them on start. New sessions always receive the secrets on connect, so this only affects an agent that was already live when you injected.
Like secret snippets and macros, profile values are backed by the iOS Keychain and can be gated by Face ID, Touch ID, or your device passcode. Profiles are handy for giving an agent or a shell its API keys and tokens without those values ever landing in your scrollback.
Manage profiles in Settings → Security → Secret Profiles, or open the Secret Profiles tile on the keyboard palette's Features page.
17. Macro bar
The macro bar is a scrollable row of one-tap commands above the keyboard, available in every session. Tap a macro to send its command followed by Return.
Editing macros
Open Settings → Keyboard → Macros:
- Add a macro - a short label (what shows on the bar) and the command text.
- Edit by tapping a row.
- Reorder by dragging.
- Delete by swiping.
- Restore defaults to bring back the built-in starter set.
Hiding the macro bar
Settings → Keyboard → Show Macros turns the row off entirely if you'd rather have the screen real estate back.
Secret macros
Like secret snippets, macros can be marked secret and gated by Face ID. Useful for fast-fire credentials without the macro text appearing in the bar.
18. SFTP file browser
The SFTP browser moves files on and off your servers without leaving meshTerm. It runs over the same SSH transport as your terminal sessions - no additional credentials, no third-party cloud.
Opening the browser
- From the host detail screen, tap Browse files.
- meshTerm opens an SFTP channel over the existing SSH connection and shows the home directory.
Navigating
- Tap a folder to enter it; tap Back in the navigation bar to go up.
- Pull down to refresh.
- The current path is shown in the title bar.
Downloading
- Tap a file to download. A progress indicator shows bytes transferred.
- When the download completes, the iOS Files share sheet appears - pick iCloud Drive, On My iPhone, or any Files-compatible provider.
Uploading
- Tap Upload File in the browser toolbar.
- Pick one or more files from the iOS Files sheet.
- meshTerm uploads them to the current remote directory.
File operations
Long-press a file or folder for options:
- Rename - enter a new name.
- Delete - confirm and the file or folder is removed. Non-empty folders aren't deleted in one pass - empty them first.
- New Folder (toolbar) - create a directory in the current path.
19. Scrollback search 2.0
Press ⌘ F (or tap the magnifier in the terminal toolbar) to open the search bar at the bottom of the terminal. Type to find matches in the visible scrollback; a running match count shows how many hits.
- Up / Down arrows - step between matches.
- Smart Case - case-insensitive unless your query contains an uppercase letter.
- Done - close the search bar and return to the live prompt.
The scrollback is capped at 5,000 lines by default. On Roam hosts you can search server-side with mtroam search for matches beyond the local cap.
20. Color modes
Settings → Terminal → Color Mode sets the terminal's look. Alongside full True Color, meshTerm offers retro single-hue modes for a calmer, CRT-style view:
- True Color - full 24-bit color.
- Monochrome - a single neutral hue.
- Green Phosphor, Blue Phosphor, Red Phosphor - classic terminal glows.
The single-hue modes collapse color to one tint but keep diffs legible: additions render bright, removals dim. The change applies instantly to the open terminal.
21. Security & App Lock
Known Hosts (TOFU)
meshTerm uses Trust On First Use for host-key verification. The first time you connect to a host, the app shows the server's host-key fingerprint and asks you to confirm. Subsequent connects check the fingerprint silently. If it changes (server rebuilt, key rotated, or something more sinister), the connection is blocked with a warning.
Manage trusted fingerprints in Settings → Security → Known Hosts. Swipe an entry left to delete it. If a server's key legitimately changed, delete the old entry and reconnect to accept the new fingerprint.
App Lock
Settings → Security → App Lock requires Face ID, Touch ID, or your device passcode to reopen meshTerm. Pick one of:
- Off
- Immediately - lock the moment the app backgrounds.
- After 30 seconds / 1 minute / 5 minutes - short app switches pass through.
Cold launch always requires unlock if App Lock is on.
Credential storage
Everything sensitive lives in the iOS Keychain:
- SSH private keys (Ed25519, ECDSA).
- SSH passwords.
- Tailscale API tokens.
- Secret snippets and secret macros.
By default nothing sensitive is written to UserDefaults, the filesystem, or an iCloud device backup in plaintext. If you enable iCloud Sync, secrets sync only through iCloud Keychain, which is end-to-end encrypted.
22. iCloud Sync
iCloud Sync keeps meshTerm consistent across your iPhone and iPad. It is opt-in and off by default - turn it on in Settings → iCloud Sync - and it is a Pro feature.
What syncs
- Your hosts, host tags, and known-host trust (the fingerprints you have accepted).
- Your SSH key list, macros, snippets, and Tailscale profile names.
- Appearance and terminal preferences. Some are kept per device class, so an iPhone and an iPad can differ where that makes sense.
Secrets
Sensitive material - SSH private keys, passwords, and secret snippets and macros - is never placed in the synced records. It rides iCloud Keychain instead, which is end-to-end encrypted, so it reaches your other devices without ever being exposed to iCloud in plaintext. Tailscale credentials (auth keys and API tokens) never sync; each device signs in for itself.
Turning it off
Turning sync off stops further propagation but never deletes anything already on a device. To remove synced data, use the erase options under Settings → Security & Privacy.
23. Settings reference
A quick map of the Settings tab.
- Subscription - current Pro state and upgrade buttons.
- Appearance - theme picker.
- Terminal - font size, scrollback options, TUI pill master toggle.
- Keyboard - Customize Keyboard, Minimized Row, Left-Handed Chrome, Open Sessions Minimised, Tap-for-Keyboard Reminder, Button Size, Show Macros, Macros editor.
- Connections - default persistence and default Roam idle timeout for newly added hosts.
- SSH Keys - generate, import, copy, delete.
- Hosts - import from
.ssh/config, export tossh_config. - Security - App Lock and Known Hosts.
- Tailscale - API Token, Auth Key, embedded node status.
- Privacy - clear local connection history.
- About - version, Privacy Policy, EULA.
24. meshTerm Pro
Every feature in the app is available in the free tier. The only limit is on concurrent surface area:
| Free | Pro | |
|---|---|---|
| Saved hosts | 1 | Unlimited |
| Concurrent sessions | 1 | Unlimited |
| Concurrent agents (Agent Mode) | 1 | Unlimited |
| Roam, predictive echo, history browse, TUI pill, wedge recovery | Yes | Yes |
| SSH key auth (Ed25519, ECDSA), key generation / import / install | Yes | Yes |
| Tailscale SSH and Tailnet device browsing | Yes | Yes |
| Snippet Vault (including secret snippets) | Yes | Yes |
| Macro bar (including secret macros) | Yes | Yes |
| SFTP file browser | Yes | Yes |
| Themes, scrollback search, host import/export, tags | Yes | Yes |
| App Lock | Yes | Yes |
Subscribing
Tap Upgrade in Settings or on the host-limit / session-limit prompt. Pro is available as monthly or annual; the annual plan is the better value. Free trials, when offered, apply on the first qualifying purchase per Apple ID.
Restoring purchases
Tap Restore Purchases in Settings if you reinstall the app or switch devices.
Cancelling
Manage or cancel a subscription via iOS Settings → Apple ID → Subscriptions, or via the Subscriptions row inside the App Store app.
25. Troubleshooting
Connection
"Could not resolve [hostname]"
- Check the hostname is spelled correctly.
- Check your device has a working internet connection.
- If using a local hostname (e.g.
home-server.local), ensure you're on the same network.
"Could not connect"
- Verify the server is reachable and SSH is running (default port 22).
- If you're on a local network address, grant meshTerm Local Network permission in iOS Settings → Privacy & Security.
- Firewalls and NATs can drop idle SSH; set
ClientAliveIntervalon the server if drops are frequent.
Connection hangs at "Connecting…"
- Check your network.
- If connecting via Tailscale, ensure the Tailscale app is open and connected.
- Try toggling Tailscale off and on.
Authentication
SSH key auth fails silently
- Confirm the public key is in
~/.ssh/authorized_keyson the server. - The
~/.sshdirectory needs permissions700and~/.ssh/authorized_keysneeds600. - Check
sshd_configon the server hasPubkeyAuthentication yes.
"Unsupported key type"
meshTerm 2.1 accepts Ed25519 and ECDSA (P-256, P-384, P-521). RSA keys aren't supported. Generate a new Ed25519 key in the app, or convert: ssh-keygen -t ed25519.
Roam
Install fails at "Verifying"
Very rare. Means the downloaded binary didn't match the signed checksum. Retry once; if it persists, your network is corrupting downloads (an aggressive proxy?) - try a different network, or fall back to plain SSH for that host.
Install fails at "Installing service" on Linux
The most common cause is systemd-user not being able to talk to its session bus, usually because the user isn't lingering. The sheet surfaces the exact remediation: run sudo loginctl enable-linger <username> on the host and try the install again. If linger can't be enabled, the install drops to a nohup-supervised fallback and tells you so.
"PATH configured" smoke check fails
Means meshTerm's PATH addition didn't land in your shell's rc file (some custom shells aren't covered: bash, zsh, fish, and POSIX ~/.profile are). Add export PATH="$HOME/.local/bin:$PATH" to your shell config manually and re-run the smoke checks.
Daemon update fails with ETXTBSY
Shouldn't happen - the self-update path uses atomic rename. If you see it, use Reinstall from the host detail screen instead. Reinstall stops the daemon first, which ends any active sessions; save your work before you tap it.
"Daemon firewall port" instructions
The install sheet's success state shows a distro-specific one-liner to open the daemon's UDP port (default 49820). If you skip it, the daemon still works for SSH-tunnelled attaches, but direct attaches over the network won't reach it.
Roam silently fell back to plain SSH
If the daemon is unreachable at connect time, meshTerm falls back to a plain SSH session and shows an orange banner above the terminal so the drop isn't silent. Tap the banner for the underlying error. Common causes: the daemon isn't running on the host (run mtroam status or check via SSH), or the firewall port isn't open.
Tailscale
Tailnet device browser is empty or asks for a token
When meshTerm is connected to your tailnet (Built-in mode), the device list comes straight from the tailnet with no API token needed. A read-only API token is only needed to browse while you are not connected (offline, or a profile you haven't signed into). Add one under Settings → Connections → Tailscale if you want that. See the Tailscale Setup Guide.
"Invalid or expired token."
Tailscale API tokens expire. If you use one, generate a new one at login.tailscale.com and paste it into Settings → Connections → Tailscale.
Tailnet devices missing from the browser
- Only authorised devices appear. Check your Tailnet's device-authorisation settings.
- Confirm the API token was generated for the correct Tailnet.
- Pull to refresh.
Security warnings
Host key mismatch
The server's host key has changed since you last connected. This can mean a legitimate change (server rebuilt, keys rotated) or something more suspicious. If you're confident the change is legitimate:
- Settings → Security → Known Hosts.
- Find the entry and swipe to delete.
- Reconnect; you'll be asked to trust the new fingerprint.
If you're not sure, do not reconnect. Investigate the server directly first.
App Lock
"meshTerm requires a device passcode."
App Lock can't be enabled without a device passcode. Set one in iOS Settings → Face ID & Passcode, then return to meshTerm.
SFTP
"SFTP subsystem not available"
The server doesn't have the SFTP subsystem enabled. In OpenSSH, ensure /etc/ssh/sshd_config contains Subsystem sftp /usr/lib/openssh/sftp-server (the exact path varies by distro) and restart sshd.
Permission denied on rename / delete / mkdir
Your account needs write permission on the target directory. Some SFTP servers reject cross-filesystem renames - keep the target in the same directory.
Purchases
"Purchase could not be verified."
There was a problem verifying with the App Store.
- Check your internet connection.
- Try Restore Purchases in Settings.
- If the issue persists, contact Apple Support via the App Store.
meshTerm is developed by James Betchley. For support, contact meshterm@gmail.com.