By what you see: symptom, why, fix.
1. Getting connected
Most connection failures show a failure page with the cause in plain words and the next step as buttons, such as Edit Host, Copy Public Key, Show Install Command, Server Setup Help and Reconnect. For the short version of the basics (hostnames that do not resolve, a connection that hangs at "Connecting...", Tailscale), see Troubleshooting in the user guide.
"HOST refused the connection on port 22. Check that SSH is running on that port."
WhyThe host answered, but nothing is listening for SSH on that port. The SSH server (sshd) is not installed, is stopped, or listens on a different port. A brand-new server often has no SSH server yet.
Fix
Tap Server Setup Help on the failure (it is also on Add Host's first step as New server? How to turn on SSH). It lists the commands for your system. You need console access to the server, for example your provider's web console, because the phone cannot reach it yet.
# Debian, Ubuntu
sudo apt-get update && sudo apt-get install -y openssh-server
sudo systemctl enable --now ssh
# Fedora
sudo dnf install -y openssh-server && sudo systemctl enable --now sshd
# Alpine (as root)
apk add openssh && rc-update add sshd && rc-service sshd start
On a Mac, turn on Remote Login in System Settings, under General, Sharing. To check which port sshd uses and whether it is listening, run sudo ss -tlnp | grep sshd, then put that port in Edit Host.
The service is named ssh on Debian and Ubuntu and sshd on Fedora. A related message, "accepted the connection but didn't start SSH", means something is listening on the port but it is not an SSH server (or it is still starting). Check that sshd is running on that port.
"Couldn't reach HOST:22. Check that the host is online and on the same network or tailnet." / "The Host Didn't Answer"
WhyNothing came back at all. Usually a firewall on the server or a cloud security group is dropping the traffic, the IP address is wrong, the server is off, or it has only a private address and you are not on that network. A refused connection (above) means the host answered; a timeout means it did not.
Fix
- Check the address and that the server is running. In your provider's console, confirm the public IP.
- Cloud servers: open the provider's firewall (security group, firewall rule) for TCP port 22, or your own SSH port. Allowing only your own IP is safer than allowing everyone.
- Tailscale needs no open port. If both ends are on your tailnet, connect by the host's tailnet name or 100.x address and close port 22 to the internet. See the Tailscale Setup Guide.
- Host on your home or office network: grant meshTerm the Local Network permission in iOS Settings, under Privacy & Security.
- Server firewall on the host itself. Server Setup Help shows these with your port filled in; they open only the SSH port:
# Ubuntu, Debian (ufw)
sudo ufw allow 22/tcp
# Fedora (firewalld)
sudo firewall-cmd --permanent --add-service=ssh && sudo firewall-cmd --reload
If connections work and then drop when idle, a firewall or NAT in between is closing quiet connections. Set ClientAliveInterval 60 in /etc/ssh/sshd_config and reload sshd.
"HOST rejected the password for USER. Check the username and password in Edit Host."
WhyEither the username or password is wrong, or the server does not allow password sign-in for that account. Many cloud images turn password sign-in off, and many servers refuse root's password altogether (PermitRootLogin). For root, the app adds: "Many servers refuse root's password (PermitRootLogin). Sign in as a normal user, or use an SSH key."
Fix
- Open Edit Host and re-enter the username and password. Usernames are case sensitive.
- Sign in as a normal user instead of root. Cloud images usually create one for you, for example
ubuntu on Ubuntu images or ec2-user on Amazon Linux. The username field's hint reads "ubuntu, admin, your-name...".
- Better, use an SSH key (see server only accepts SSH keys). We recommend against turning on
PermitRootLogin yes.
A saved password that is wrong can repeat on every reconnect and may trigger fail2ban. Fix the password first.
"HOST only accepts SSH keys for USER. Add a key to the server (Show Install Command), then choose it in Edit Host."
WhyThe server has PasswordAuthentication no, which is the default on most cloud images and a good security setting. Only a key will work.
Fix
On the failure page:
- Show Install Command opens a sheet with the key's one-line
authorized_keys command. For a password host it lets you pick a key or tap Generate a New Key.
- Copy Public Key copies the key in one tap (the button flips to "Copied").
- Install on Host appears only when another saved host reaches the same server and user with a different working sign-in (a saved password, Tailscale SSH or a different key). It installs the key through that host.
Otherwise run the command once on the server from a session that already works, your provider's web console, or cloud-init. The command the app shows appends your public key to the file:
mkdir -p ~/.ssh && chmod 700 ~/.ssh
printf '\n%s\n' 'ssh-ed25519 AAAA...your-public-key... meshterm' >> ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys
Then choose that key in Edit Host and tap Reconnect. Run it once: running this by hand twice adds the key twice (Install on Host in the app does not).
"HOST rejected the SSH key for USER. Add its public key to ~/.ssh/authorized_keys for that user, or choose another key in Edit Host."
WhyThe server does not recognise the key. The public key is not in that user's authorized_keys, the key in Edit Host is a different one, or the file or folder has permissions sshd refuses to trust.
Fix
- Use Copy Public Key and Show Install Command as above, and check that Edit Host uses the key you installed.
- Check the server side. sshd silently ignores keys when the folder or file is too open:
chmod 700 ~/.ssh && chmod 600 ~/.ssh/authorized_keys
chmod go-w ~ # the home folder must not be writable by others
sudo grep -i pubkeyauthentication /etc/ssh/sshd_config # should not say "no"
- "didn't accept key sign-in for USER on its own": either key sign-in is off for this user, or the server also wants a password after the key (
AuthenticationMethods), which meshTerm does not support yet. Switch to password sign-in in Edit Host, or allow key-only sign-in on the server.
- Alpine: an account created with
adduser -D is locked, and sshd refuses even key sign-in ("account is locked"). Set a password once with passwd USER (as root); that unlocks it.
- Check that the key type is supported: see RSA keys can't be used.
"RSA keys can't be used in this app yet. Generate an Ed25519 key instead." / "Unsupported key type"
WhymeshTerm signs in with Ed25519 and ECDSA (P-256, P-384, P-521) keys. RSA user keys are not supported, so an imported RSA private key is refused.
Fix
- On the import error, tap Generate an Ed25519 Key Instead. It makes the key (named after your import) and moves to Add the Key to the Server.
- That step shows the one-line
authorized_keys command with Copy and Copy Public Key. Run it on the server. It adds the new key next to your RSA key, which keeps working for your other apps.
- Add Host has the same shortcut: Generate a New Key beside Import. Existing keys: SSH Keys, open a key, Add It Yourself.
From a computer that can already sign in, the classic way also works:
ssh-keygen -t ed25519
ssh-copy-id -i ~/.ssh/id_ed25519.pub USER@HOST
Then import that key into meshTerm. See SSH keys in the guide.
"Couldn't set up a secure connection with HOST: it most likely only offers an RSA host key, which meshTerm can't use yet."
WhyThe server proves who it is with a host key. meshTerm understands Ed25519 and ECDSA host keys, not RSA. A server that offers only an RSA host key (an old install, or a HostKey line that lists only the RSA file) cannot complete the handshake. The app cannot tell this apart from other handshake problems, so it names the likeliest cause.
Fix
On the server, create the missing host keys, including Ed25519, then reload sshd:
sudo ssh-keygen -A
grep -i '^HostKey' /etc/ssh/sshd_config # if present, make sure an ed25519 line is listed
# reload sshd
sudo systemctl reload ssh # Debian, Ubuntu
sudo systemctl reload sshd # Fedora
sudo rc-service sshd reload # Alpine
After this, meshTerm shows the unknown host key prompt as for any new host (see the next entry).
"Unknown Host Key" and "Host Key Changed"
WhyThe first time you connect, meshTerm shows the server's key fingerprint and asks you to trust it. If a server later presents a different key, you see Host Key Changed: "the host was reinstalled or its address changed. Trust the new key only if you expected this." It can also mean someone is intercepting the connection, which is why the prompt never trusts by default.
Fix
Compare the fingerprint on screen with the server's own, from the server's console (not over the connection you are checking):
ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub
# the file follows the key type the prompt shows, for example:
ssh-keygen -lf /etc/ssh/ssh_host_ecdsa_key.pub
If the fingerprints match, tap Trust Host. For a changed key: if you rebuilt or re-imaged the server, trust the new key; if you did not, do not connect and look at the server first. You can also see and remove saved keys in Settings > Hosts & Network > Trusted servers: remove one to be asked again at the next connect. Cloud consoles often print the host key fingerprints at first boot, which is another way to check.
"Host key not trusted yet" on some screens (the Containers Fleet, the Sessions list, system usage) means you have not trusted the host yet: open the host once from the Sessions tab, review the key, then try again.
"HOST only allows keyboard-interactive sign-in for USER, which meshTerm doesn't support."
WhySome servers ask questions at sign-in instead of taking a plain password: a one-time code from an authenticator app, a hardware key prompt, or a PAM password prompt on a server with PasswordAuthentication no. That is "keyboard-interactive", and meshTerm cannot answer it. This is also what two-factor authentication on SSH usually looks like, so 2FA accounts cannot sign in this way.
Fix
- Add an SSH key for this user (the safer fix): Show Install Command, as in server only accepts SSH keys. This works when the server accepts a key alone.
- A server that requires a key and a code (
AuthenticationMethods publickey,keyboard-interactive) cannot be used. Use Tailscale SSH for that machine, or give meshTerm its own account that allows a key alone.
- Or turn on
PasswordAuthentication yes in /etc/ssh/sshd_config and reload sshd. This lets anyone try passwords on that server, so use it only if you accept that, and ideally with fail2ban or a firewall in front.
"HOST closed the connection during sign-in, often a block after too many attempts (fail2ban)."
WhyTools such as fail2ban ban an IP address after several failed sign-ins. Your phone's address is then dropped or reset until the ban ends.
Fix
Wait a few minutes, fix the password or key that was failing, then reconnect. If you have console access you can lift the ban:
sudo fail2ban-client status sshd
sudo fail2ban-client set sshd unbanip YOUR.IP.ADDRESS
To stop it happening again, sign in with a key (a wrong saved password is the usual trigger) or add your tailnet or home network to fail2ban's ignoreip.
"Tailscale SSH on HOST didn't accept USER."
WhyThe host was added as a Tailscale SSH host (no password or key), but either your tailnet's SSH rule does not allow that user, or the server is not running Tailscale SSH.
Fix
Check your tailnet's SSH rule in the Tailscale admin console. On the host, turn Tailscale SSH on:
sudo tailscale up --ssh
Or switch the host to key or password sign-in in Edit Host; a Tailscale-connected host still accepts those over your tailnet. Details are in the Tailscale Setup Guide.
Pasting user@host:port, ssh://... or an IPv6 address
WhyPeople paste whatever they copied from a terminal or provider page. meshTerm sorts it out for you.
Fix
Paste it into Hostname and move on. meshTerm trims it, removes ssh:// and any path, puts the part before the last @ into an empty Username field, and puts :2222 into the Port field if it is empty or still 22. [2001:db8::1]:2222 works for IPv6; a bare IPv6 address is left alone. A password inside the URL (ssh://user:secret@host) is dropped, never saved.
"The hostname can't contain spaces or symbols. Enter just the name or IP address." means something is left over after that, such as a space or URL punctuation. If the pasted port and the Port field disagree, the form asks you to choose one.
Key installed but Windows still asks for a password
WhyThe Windows SSH server reads a different file depending on the account. A standard account uses C:\Users\<you>\.ssh\authorized_keys. An administrator uses C:\ProgramData\ssh\administrators_authorized_keys, and Windows ignores that file unless it is locked to Administrators and SYSTEM. A server can also point AuthorizedKeysFile somewhere else, such as C:\ProgramData\ssh\keys\..., where a standard account can't write. Wrong permissions on the file have the same effect.
Fix
- Use Install Key on the host. meshTerm picks the right file for the account. If it says an administrator needs to add the key, send that person the public key and the file name it shows.
- If meshTerm says it added the key but you should check, look at
AuthorizedKeysFile in C:\ProgramData\ssh\sshd_config and make sure the key is in the file it names.
- Administrator accounts: the file must be locked to Administrators and SYSTEM only. In an administrator PowerShell:
icacls.exe $env:ProgramData\ssh\administrators_authorized_keys /inheritance:r /grant *S-1-5-32-544:F /grant *S-1-5-18:F
Then try again. Details are in Windows hosts.
Permission denied signing in to a Windows host
WhyThe Windows SSH server decides who may sign in. Many servers allow only members of Administrators or the OpenSSH Users group (an AllowGroups line in C:\ProgramData\ssh\sshd_config). A standard account that is in neither is refused, even with the right password or key. An account that has never signed in to Windows also has no profile yet.
Fix
Ask an administrator to add the account to OpenSSH Users. In an administrator PowerShell:
net localgroup "OpenSSH Users" USER /add
If the group doesn't exist, check AllowGroups in the config for the group the server uses. Make sure the account has signed in to Windows once, then try again.
Tailscale SSH is missing for a Windows host
WhyTailscale SSH (Tailscale's own SSH server with tailnet sign-in) isn't available on Windows, so meshTerm doesn't offer it for Windows hosts. A device that Tailscale reports as Windows starts on password sign-in.
Fix
Connect to the machine's tailnet name or 100.x address with a key or a password, like any other host. The Windows OpenSSH server must be running on it. See Windows hosts.
2. Terminal and persistence
Persistence is chosen per host: tmux, mtRoam or None. See Persistent sessions and Check persistence in the guide.
"No session persistence": "tmux isn't installed on HOST, so this session won't persist."
WhyThe host is set to tmux but has no tmux, so you got a plain shell that ends with the connection. Fresh servers rarely have tmux.
Fix
Tap Install tmux on the banner. meshTerm pastes the install command for your system at the prompt; you read it and press Return, and it never runs by itself. In the Sessions list, "tmux wasn't found" offers Install tmux in Terminal, which opens a terminal that runs the command shown above it. Which command you get depends on who you are on the server:
- root: no
sudo.
- A user with sudo: the
sudo form. sudo asks for your password in the terminal.
- A host with no sudo (fresh Debian, Ubuntu, Alpine):
su -c '...', with "Needs the root password."
- An account that cannot use sudo (not in the sudo, wheel or admin group): the sudo form still shows, with "If your account can't use sudo, ask the server's admin to run this." If it fails, sign in as root or an admin account, or ask the server's admin.
# Debian, Ubuntu
sudo apt-get update && sudo apt-get install -y tmux
# no sudo on the host
su -c 'apt-get update && apt-get install -y tmux'
# as root
apt-get update && apt-get install -y tmux
# Fedora, RHEL, Rocky, Alma sudo dnf install -y tmux
# Amazon Linux 2, CentOS 7 sudo yum install -y tmux
# Alpine (as root) apk add tmux
# openSUSE sudo zypper install -y tmux
# Arch sudo pacman -S --noconfirm tmux
Prefer not to install it? Set the host's Persistence to None to stop the banner. The mtRoam alternative is in the guide.
"tmux wasn't found on HOST. ... If it's already installed, add its folder to PATH in ~/.bashrc or ~/.zshenv."
WhyA tool installed with Homebrew on a Mac lives in /opt/homebrew/bin (Apple silicon) or /usr/local/bin, and the PATH an SSH command gets on macOS is only /usr/bin:/bin:/usr/sbin:/sbin. meshTerm looks in /usr/bin, /usr/local/bin, /opt/homebrew/bin and /opt/local/bin as well, so a Homebrew tmux is normally found. If yours still is not, it is installed somewhere else or not at all.
Fix
No tmux yet? The command is brew install tmux (never with sudo). "No Homebrew on this Mac? Install it first from brew.sh, then run the command." If tmux is installed in an unusual place, add its folder to your PATH in a file that non-interactive SSH reads. ~/.profile is not read by SSH commands, so use ~/.zshenv (zsh, the Mac default) or the top of ~/.bashrc:
brew install tmux
echo 'export PATH="/opt/homebrew/bin:$PATH"' >> ~/.zshenv
"tmux 3.1 is too old (3.2 or newer needed)." / "Using classic tmux"
WhyNative panes and the tmux palette page use tmux control mode, which needs tmux 3.2. With an older tmux, meshTerm falls back to the classic tmux attach by itself, so the session still persists, just without native panes. Some long-term-support distros still ship older versions (Ubuntu 20.04 and Debian 11, for example).
Fix
tmux -V
A newer tmux usually comes from your distro's backports or EPEL, a newer distro release, or a static build. After upgrading, tap Check persistence on the host's Details so meshTerm tries control mode on the next connect. Nothing is broken if you leave it: sessions persist either way.
"Couldn't start tmux control mode on HOST." / control mode misbehaves on one host
WhyControl mode is the default way meshTerm talks to tmux 3.2 or newer. Unusual shell start-up files, wrappers around tmux, or restricted shells can interfere.
Fix
Reconnect to try again, or use classic tmux for that host: Edit Host > Session > tmux attach > Legacy. Other hosts keep control mode, and you can switch back any time. See Control mode or Legacy.
"mtRoam isn't installed on this host. Using tmux."
WhyThe host's Persistence is mtRoam, but the mtroamd daemon is not on the host. meshTerm cannot install it for you from the app, so it carries the session through tmux instead.
Fix
Tap Switch to tmux to make tmux the host's setting for good, or Install guide to install mtroamd (see Installing mtroamd). If tmux is missing too, the first banner is "No session persistence" with Install tmux; after you dismiss it, the mtRoam notice offers Install tmux as well. After installing mtroamd, tap Check persistence on the host's Details to use it straight away. For "Connection Failed" on an mtRoam host and the other mtRoam messages, see the guide's troubleshooting.
Garbled accents, boxes or odd characters in some programs (locale)
WhyAn SSH command without a login does not always get a UTF-8 locale. Measured on fresh images: Debian 12 and Ubuntu 22.04 give the plain POSIX locale, Ubuntu 24.04 and Fedora give C.UTF-8, a Mac gives C, and Alpine has no locale tool. Many servers do not accept the client's language setting either (Fedora and Alpine's sshd have no AcceptEnv), so it is dropped. UTF-8 text itself passes through fine; only programs that check the locale can misbehave.
Fix
locale # what does this session have?
echo 'export LANG=C.UTF-8' >> ~/.bashrc # Linux (use ~/.zshenv for zsh)
echo 'export LANG=en_US.UTF-8' >> ~/.zshenv # macOS
Put it in ~/.bashrc or ~/.zshenv, not ~/.profile, so that non-interactive SSH commands see it. Inside tmux, tmux -u forces UTF-8 for a client.
5. Agents
Claude, Codex and Gemini run on your host, not on the phone. The host needs each tool's CLI installed and signed in. They install into your home folder, so once the host has curl (or npm or Homebrew), no root access is needed. See Agent Mode.
"Prepare This Host" in Set Up Agents
WhyThe official installers are scripts that download the CLI, so the host needs curl and bash (Claude's and Antigravity's installers are bash scripts; Codex's is plain sh). A bare server may have neither: a fresh Debian 12 has no curl; Ubuntu has none either; Alpine has neither curl nor bash. When no curl, npm or Homebrew is found and an agent is missing, Set Up Agents shows Prepare This Host. Alpine hosts without bash say "no bash" up front.
Fix
Tap Run in Terminal on the line shown for your system (it opens a terminal on the host and types the command; sudo or su asks for its password there), then Check Again. Same commands by hand:
# Debian, Ubuntu
sudo apt-get update && sudo apt-get install -y curl ca-certificates
# Fedora
sudo dnf install -y curl ca-certificates
# Alpine (as root; also gets bash)
apk add curl bash ca-certificates
# no sudo on the host: su -c 'apt-get update && apt-get install -y curl ca-certificates'
# root: leave out sudo
A Mac already has curl and bash. Gemini goes through Antigravity's agy CLI.
Sign-in fails or spins: "Codex said: ..." / "Starting sign-in..."
WhyCodex signs in with a device code. Some ChatGPT accounts need that turned on first. When the sign-in ends with an error before a link or code appears, meshTerm shows the last line Codex printed. Under "Starting sign-in..." the host's own output appears after about ten seconds if nothing else does.
Fix
- Codex: in ChatGPT, open Settings > Security and turn on device code sign-in. Then Try Again. Open Terminal runs
codex login on the host yourself; codex login status shows where you stand.
- Claude: the failure quotes Claude's last line.
claude auth status shows which account is signed in and claude auth login signs in.
- Gemini: open Set Up Agents on that host, follow Google's link and paste the code back within about a minute.
agy cannot always report whether it is signed in, so if an agent fails to start, sign in again there.
- If the sign-in cannot find
node, see the CLI isn't on the SSH PATH.
"NAME needs node on the SSH PATH." / the CLI is installed but "not found"
WhyAgents start through a non-interactive SSH command, which does not read your interactive shell set-up. A CLI installed with npm is a #!/usr/bin/env node script, and nvm only puts node on the PATH in interactive shells. Homebrew's folder (/opt/homebrew/bin on a Mac) is not on the SSH PATH either. meshTerm adds the usual places for you (~/.local/bin, ~/.npm-global/bin, /usr/local/bin, /opt/homebrew/bin, Linuxbrew, volta, mise and asdf shims, fnm's default), but nvm has no fixed folder.
Fix
Run this in a terminal on the host (where node works), then tap Check Again. The app shows the same line:
mkdir -p ~/.local/bin && ln -s "$(command -v node)" ~/.local/bin/node
For a CLI itself that cannot be found, link it the same way, for example ln -s "$(command -v codex)" ~/.local/bin/codex. Putting an export PATH=... line in ~/.profile does not help, because SSH commands do not read it; use ~/.bashrc (near the top) or ~/.zshenv.
"Folder Not Found" when starting an agent
WhyThe working folder you chose (for example ~/new-project) does not exist on the host. A ~ is expanded to the host's home folder.
Fix
Tap Create Folder (it runs mkdir -p and starts the agent there; if it fails, an alert says why), or Use Home Folder for an agent that has not run yet. Claude files its conversations per folder, so Use Home Folder is not offered for an agent that already has history.
Using an API key instead of signing in (Use an API Key Instead)
WhySign-in is the default. An API key is the alternative when browser sign-in is not possible or you pay per use.
Fix
The setup sheet explains it per agent:
# Claude: near the top of ~/.bashrc, or in ~/.zshenv (NOT ~/.profile)
export ANTHROPIC_API_KEY=your-key
# Codex: set OPENAI_API_KEY in a terminal on the host, then
printenv OPENAI_API_KEY | codex login --with-api-key
~/.profile does not work because an SSH command runs $SHELL -c and never reads it. Codex keeps the key with its own sign-in, which every SSH command sees. Keep files that hold keys readable by you only (chmod 600). With Pro, Secret Profiles can hold keys on the phone instead. Gemini has no API-key option in the app.
Agents on a Mac host
WhyA stock Mac differs from Linux: no setsid or timeout, sleep infinity is rejected, /usr/bin/python3 and /usr/bin/git are stubs that ask to install Apple's Command Line Tools, and the SSH PATH is only /usr/bin:/bin:/usr/sbin:/sbin. meshTerm works around the first ones itself (it starts agents with setsid or else nohup, and time-limits checks with timeout, gtimeout or perl).
Fix
xcode-select --install # real python3 and git (notifications and live status need python3)
brew install coreutils # optional: gives gtimeout
Make sure Remote Login is on, and see Homebrew on a Mac and PATH for tools that are installed but not found. "python3 needs Apple's command line tools" in herdr or notifications means the stub, not a real python3.
"This host blocks unprivileged user namespaces and has no bubblewrap, so Codex's sandbox can't start and it will ask approval for every command."
WhyCodex isolates commands with bubblewrap, which needs the package and a kernel that lets ordinary users create namespaces. Related messages: "Harden with Bubblewrap is on, but bubblewrap isn't installed", and "bubblewrap is installed but can't start a sandbox, usually because unprivileged user namespaces are turned off".
Fix
Install it (Set Up Agents shows the line for your system, with Copy):
# Debian, Ubuntu
sudo apt-get update && sudo apt-get install -y bubblewrap
# Fedora
sudo dnf install -y bubblewrap
# Alpine (as root)
apk add bubblewrap
If it is installed but cannot start, namespaces are off. Check with sysctl user.max_user_namespaces (0 means off). Ubuntu 24.04 also restricts them with AppArmor (kernel.apparmor_restrict_unprivileged_userns); loosening that setting weakens a protection for the whole machine, so decide deliberately. Or turn off Harden with Bubblewrap in the agent's settings. Without a working sandbox, Codex simply asks for approval for each command.
6. Notifications
Agent notifications use a small hook on the host and Apple's push service through the relay. See Notifications in the guide.
Notifications don't arrive
WhyThe host setup, the agent's session or the phone's notification settings may be stopping delivery.
Fix
First open Edit Host and tap Send Test Alert. Turn on Notify this iPhone or Notify this iPad and save first if needed. The test checks this host's path to this phone; a successful test does not prove a running agent has loaded its hooks. Ayuda en espaƱol.
- Not checked yet: there is no saved result. Use Send Test Alert to check, or Install Now to set up the host. Installed on this host means setup succeeded; test delivery next.
- Not installed, or the test finds missing notification files: tap Install Now.
- Missing Python or curl: run the install command shown on the host, then tap Try Again. See Host tools.
- "Couldn't install:" or a failed last check: correct the stated cause, then tap Try Again. Check SSH access; a folder that is not private to your user needs its ownership and permissions fixed, and shell startup files must not print text or read input during non-interactive SSH commands.
- "This device's registration changed. Tap Reinstall to update this host." or "This device's registration on the host is out of date.": tap Reinstall.
- "The relay no longer has this device's registration.": tap Register Again to register this phone again and refresh the host's details.
- "Codex alerts start once Codex trusts the hook. Tap Try Again.": tap Try Again.
- "The relay refused the test alert's format. Tap Reinstall.": tap Reinstall. If the host cannot reach the relay or does not confirm the test, tap Try Again; if the relay is busy, wait before retrying.
- The relay accepted the test but it did not arrive: tap Open Settings, allow meshTerm notifications and check Focus. Also check the two switches in Settings > Agents > Notifications.
If the test arrives but agent alerts do not, exit and restart the agent after hooks are installed. Run it in a meshTerm-managed tmux or mtRoam session, a herdr pane, or a chat in the Agents tab. A plain SSH tab or your own tmux session started outside meshTerm has no session record telling the host which phone should receive the alert. On Windows use psmux for Claude, Codex or agy (Gemini). Codex in herdr panes alerts through herdr; enable Notify for herdr Agents for herdr alerts.
On Windows and Linux, when two Codex windows share a folder, meshTerm can identify only one and the other can miss alerts. codex resume <id> always identifies the session for routing. With more than one phone or tablet set up for alerts on the host, only windows launched with codex resume <id> send Codex's background alerts. Use separate folders or resume the specific session. On macOS, run terminal Codex as codex --no-daemon, including in herdr panes, or use the Agents tab.
For question alerts such as "Claude has questions.", turn on Agent needs approval in Settings > Agents > Notifications. It controls both questions and approvals. Tap a question alert to open the session, then answer in the terminal; the alert has no answer or approval action. See Notifications for supported agents.
Notifications need Python 3.5 or later and curl
WhyLinux and macOS alerts need both tools. Python 2 alone, or Python older than 3.5, is not enough: the app says "Notifications need Python 3.5 or later on HOST. Its Python is older." A Mac's /usr/bin/python3 may only be a placeholder until the Command Line Tools are installed.
Fix
Run the command the host's status row shows, then tap Try Again. The agent chat banner also offers Run in Terminal. For example:
# Debian, Ubuntu
sudo apt-get update && sudo apt-get install -y python3 curl ca-certificates
# Fedora
sudo dnf install -y python3 curl
# Alpine (as root)
apk add python3 curl ca-certificates
# Mac: install the Command Line Tools
xcode-select --install
If Python was found but did not start, set a global Python version in pyenv or asdf and tap Try Again. meshTerm also searches Homebrew, conda and ~/.local/bin; see Host requirements. If the test says hooks cannot find or start Python, tap Reinstall. If it says agents started outside a login shell cannot find curl, install the system's curl so it is available in /usr/bin or /bin.
Windows: restricted PowerShell or old curl
WhyAlerts use Windows PowerShell 5.1 and the built-in curl.exe, with no Python dependency. Constrained Language Mode blocks setup; older Windows versions may lack curl or send alerts more slowly.
Fix
- PowerShell Is Restricted: "PowerShell is restricted on HOST, so meshTerm can't turn on notifications there." Ask the host's administrator about the policy; retrying alone will not fix it.
- Missing
curl.exe: update Windows, then tap Try Again. Windows 10 version 1803 and later include it.
- "This host's curl.exe is older than 7.68, so alerts go to your devices one at a time.": alerts still work, one phone at a time. Updating Windows or curl can speed delivery to multiple devices.
"Notifications are turned off for meshTerm in iOS Settings."
WhyThe host side may be fine, but iOS itself is not allowed to show meshTerm's alerts.
Fix
Tap Open Settings where it is offered, or go to iOS Settings > Notifications > meshTerm and turn on Allow Notifications. Check that no Focus mode is silencing it.
"Live Activities are off for meshTerm in iOS Settings."
WhyThe lock-screen and Dynamic Island status for running agents needs the Live Activities permission for the app.
Fix
In iOS Settings, open meshTerm's settings and turn on Live Activities. Alerts still arrive without them.
7. herdr
herdr keeps Claude, Codex and Gemini running on your host, each in its own pane. See herdr in the guide.
"meshTerm alerts aren't set up" / "Open this host in meshTerm" means the meshTerm plugin was installed outside the app and no device is set up for alerts. herdr attempts this notice at most once a week; its Set up meshTerm alerts action offers setup guidance on demand (a toast on Windows, if herdr's toast settings allow it). Open the host's herdr dashboard in meshTerm and tap Turn On in Turn on herdr alerts for this iPhone (or iPad / device).
"Set Up herdr", "herdr X is too old", "herdr has no build for HOST"
Whyherdr runs on your host, so the host needs herdr 0.9.2 or later in your home folder, plus python3 for live status (without it status updates only every few seconds, shown as "No python3: updates every few seconds"). Set Up herdr checks the host and installs herdr's official build in one tap, with no admin rights. Its installer needs curl, awk and a SHA-256 tool. Windows hosts need herdr 0.9.4 or later: see herdr install stopped on Windows.
Fix
- Missing tools: the sheet shows the install line for your system. Run it, then Check Again. For Debian and Ubuntu that is, for example,
sudo apt-get update && sudo apt-get install -y curl ca-certificates python3 (drop sudo as root).
- "herdr is installed but doesn't run on this host. Its standard build may not suit this system (Alpine, for example)": the standard build is not for every system. Use the install guide's other ways.
- "herdr X is in ~/.local/bin, but your login shell finds an older herdr first": update or remove the older one, then Check Again. If Homebrew installed it,
brew upgrade herdr updates it.
- "The installer finished, but HOST can't find herdr on this host": wait a moment and Check Again.
- Update herdr appears when a host's herdr is older than the app needs.
On Linux and macOS, the notification plugin needs Python 3.5 or later and curl; see Host tools. Windows alerts use PowerShell and curl.exe instead; see Windows notification requirements.
herdr install stopped on Windows
WhyOn a Windows host the herdr install runs over your connection. If the connection drops, or you leave meshTerm, the install stops part way.
Fix
Keep meshTerm open and tap Try Again. Running the install again is safe. If it keeps stopping, check that the host is reachable and that curl.exe works there (current Windows includes it). herdr 0.9.4 or later on x86_64 is needed; ARM isn't covered yet. See herdr on Windows.
herdr says this host restricts PowerShell
WhySome company policies put PowerShell in Constrained Language Mode. herdr support on Windows needs full PowerShell, so it can't run on that host.
Fix
Ask an administrator to allow it for your account, then Check Again. Or use herdr in a plain terminal on the host.
8. Containers
The Containers tab (Pro) finds Docker, Podman, nerdctl and Apple's container tool on your hosts, shows them in a Fleet, and lets you start, stop and deploy. If you are new to containers, read Docker 101 first. When something stops a host, its Fleet card shows the failure (not a spinner) with the fix. Where a command is needed, Run in Terminal opens a terminal on the host and types it for you, and Copy copies it. After you fix it, tap Check Again or Try Again. Commands that need root show "Needs an administrator", and become su -c '...' ("Needs the root password.") on hosts without sudo; as root they have no sudo. Linux hosts are covered throughout; Mac hosts (Docker Desktop, OrbStack, Colima, Podman machine and Apple's container tool) need meshTerm 2.3.
"No Container Engine Found" / "Couldn't find a container engine on HOST. Install Docker or Podman on the host, then check again."
WhyNothing on that host looks like a container engine. The Fleet lists such hosts as "N hosts have no container engine" with Show; each opens its host screen with the install lines for its system.
Fix
Install one engine, then Check Again. The full walkthrough, with the choice between Docker and Podman, is in Docker 101. The short version:
# Debian, Ubuntu: the distro package
sudo apt-get update && sudo apt-get install -y docker.io
# ...or Docker's official convenience script (any of Ubuntu, Debian, Fedora, RHEL, CentOS)
curl -fsSL https://get.docker.com | sh
# let your user talk to Docker (read the security note first), then sign in again
sudo usermod -aG docker $USER
# ...or Podman, which needs no group and can run rootless
sudo apt-get update && sudo apt-get install -y podman
# Fedora
sudo dnf install -y podman
# Alpine (as root)
apk add docker docker-cli-compose && rc-update add docker default && rc-service docker start && addgroup $USER docker
# macOS
brew install --cask docker-desktop # or: brew install --cask orbstack
Fresh Debian, Ubuntu and Alpine images have no sudo. Either sign the host in as root (every line then has no sudo and no group step), or set sudo up once as root:
su -c 'apt-get update && apt-get install -y sudo && usermod -aG sudo USER' # Debian, Ubuntu
su -c 'dnf install -y sudo && usermod -aG wheel USER' # Fedora
su -c 'apk add sudo && addgroup USER wheel && echo "%wheel ALL=(ALL:ALL) ALL" > /etc/sudoers.d/wheel' # Alpine
Replace USER with your SSH username, and sign in again. If your account is not allowed to install software at all, the app says "Your account can't install software on this host. Sign in as root or an admin account, or ask the server's admin." and keeps the command for the admin.
"Container Engine Not Running": "The container engine on HOST isn't running. Start it on the host, then try again."
WhyThe engine is installed but its service is stopped or not enabled. Related lines: "Docker is installed, but its system service isn't running and rootless Docker isn't set up", "The system Docker service is installed but turned off", and "Needs Setup" with "The engine is installed, but nothing answered. Its service may be installed but stopped."
Fix
Use the start command for your engine and init system. Run in Terminal on the card types it; Check Again afterwards.
# systemd (most Linux): system engines
sudo systemctl enable --now docker
sudo systemctl enable --now podman.socket
sudo systemctl enable --now containerd # nerdctl
# systemd: rootless engines (your own user's service, no sudo)
systemctl --user enable --now docker
systemctl --user enable --now podman.socket
# OpenRC (Alpine)
sudo rc-service docker start && sudo rc-update add docker default
sudo rc-service podman start && sudo rc-update add podman default
sudo rc-service containerd start && sudo rc-update add containerd default
# macOS
open -a Docker # Docker Desktop
open -a OrbStack
colima start
podman machine start
container system start # Apple's container tool
Debian 12 with docker.io: the dockerd program lives in /usr/sbin, which is not on the PATH an SSH command gets on Debian, so the app cannot see it and says the service "may be installed but stopped". Starting the service works: sudo systemctl enable --now docker. If a command such as dockerd or sshd is "not found" in your own terminal, add the folders: export PATH="$PATH:/usr/sbin:/sbin".
"No Permission for the Container Engine": "The SSH user on HOST can't reach the Docker socket. Add the user to the docker group ..."
WhyThe engine's socket belongs to root and the docker group, and your SSH user is in neither. Rootful Podman's and containerd's sockets are root-only too ("The SSH user on HOST can't reach rootful Podman's socket" or "containerd's socket, which nerdctl uses"). Read this first: membership of the docker group is equivalent to root on that host, and anything that can use rootful Docker can read and change the whole machine. That is how Docker works, not a flaw, and for a personal server where you are already the administrator it is a common and reasonable choice. See Docker 101: security.
Fix
sudo usermod -aG docker $USER # then sign out of the SSH session and in again
id # check that "docker" is listed
meshTerm opens a new SSH connection, so tap Try Again after you have fixed it (the group applies to new connections). Other options: set the engine up with sudo (see sudo needs a password), add the host as root, or use rootless Docker or rootless Podman, which need no group and keep containers away from the host's root. A rootless engine that cannot be reached ("can't reach their own rootless engine") has a stopped user service: start it and keep it running with lingering.
Sudo needs a password: "Not Allowed to Use Sudo", "Sudo Check Didn't Pass", "sudo on HOST couldn't run the container engine"
WhySome engines run as root, so meshTerm runs their commands with sudo -n, which cannot ask for a password. If sudo wants a password, says "sorry, try again", "a password is required", "is not in the sudoers file", or "sudo: command not found", the engine cannot be used that way. A sudo password grants nothing on the host by itself; passwordless sudo for the engine is the same as root access to the host, which the app tells you before you choose it.
Fix
- Use a Sudo Password (or Use My SSH Password when it is the same). meshTerm tests it first, before saving anything. If the test fails you see "Sudo Check Didn't Pass" and nothing is added. The password stays on this device in the Keychain and is removed with the host.
- Passwordless sudo, narrowly. If you want no password, allow only the engine program, never everything:
command -v docker # the full path, for example /usr/bin/docker
sudo visudo -f /etc/sudoers.d/meshterm-docker # then add one line:
USER ALL=(root) NOPASSWD: /usr/bin/docker
- Not a sudoer, or no sudo installed: the app shows the set-up line to run as root (see No container engine found), or add the host as root in Edit Host. Making an account a sudoer is copy-only, for root or another admin:
sudo usermod -aG sudo USER (Debian, Ubuntu) or sudo usermod -aG wheel USER (Fedora).
- Use Rootless Docker/Podman Instead is offered where it can work, and avoids sudo altogether.
Hosts under "Not Checked" in the Fleet
WhyThe Fleet lists every SSH host it could not check, with the reason and one way out, instead of hiding it. Windows hosts and hidden hosts are never listed.
Fix
| Reason on the row | What to do |
| Host key not trusted | Trust Host. It shows the fingerprint prompt (see host keys), then the host is checked. |
| No password or key on this device, so it couldn't sign in | Edit Host, add the password or key. |
| Tailscale isn't connected | Open Tailscale Settings, bring Tailscale up. |
| Not reachable, refused, no answer, not checked yet | Check Again checks that host alone. For the cause, see refused and timeouts. |
"Couldn't Check Your Hosts" means every host was skipped; "No Containers Found Yet" has Check Again too. Pull down on the Fleet to check everything again. If Find Container Hosts Automatically is off in Settings > Containers, a new host is checked only when you pull to refresh. Without Pro, the Containers tab shows a read-only preview of your hosts and engines, and pulling down checks names only.
"Compose Not Installed", "Compose on this host is too old: found 1.29.2, need 2.x or newer", git missing
WhyStacks and templates need Docker Compose v2 (the docker compose command). Compose v1 (docker-compose with a hyphen) is too old. Debian's own Compose package is the old v1. Deploying from a Git repository also needs git on the host. "Stacks and templates need Compose v2, so Templates shows in the + menu once it's installed."
Fix
# Ubuntu
sudo apt-get update && sudo apt-get install -y docker-compose-v2
# Debian: Docker's script installs the Compose plugin (the Debian package is v1)
curl -fsSL https://get.docker.com | sh
# Fedora
sudo dnf install -y docker-compose
# Alpine
sudo apk add docker-cli-compose
# macOS
brew install docker-compose
# Any Linux: the official Compose v2 binary, as Docker's CLI plugin
sudo mkdir -p /usr/local/lib/docker/cli-plugins && sudo curl -fsSL "https://github.com/docker/compose/releases/latest/download/docker-compose-linux-$(uname -m)" -o /usr/local/lib/docker/cli-plugins/docker-compose && sudo chmod +x /usr/local/lib/docker/cli-plugins/docker-compose
# Podman: it looks for a docker-compose program on the PATH
sudo curl -fsSL "https://github.com/docker/compose/releases/latest/download/docker-compose-linux-$(uname -m)" -o /usr/local/bin/docker-compose && sudo chmod +x /usr/local/bin/docker-compose
# git
sudo apt-get update && sudo apt-get install -y git # Debian, Ubuntu
sudo dnf install -y git # Fedora
sudo apk add git # Alpine
xcode-select --install # macOS
Then tap Check Again on New Stack or the host's Stacks section; meshTerm reconnects to the host so it notices the change. The standalone curl lines need curl (see fresh-server notes).
"Port Already in Use": "Port 8080 is already used on HOST. Choose another host port."
WhyAnother container or program on the host already listens on that port, so the new container cannot publish it.
Fix
Tap Edit to go back to the form and pick a free host port (only the left number in 8080:80 changes). To find what holds it:
sudo ss -tlnp | grep ':8080 '
docker ps --format '{{.Names}} {{.Ports}}'
"Rootless engines can't publish port 80 or any other port below 1024."
WhyOn Linux an ordinary (non-root) user cannot listen on ports below 1024, and a rootless engine runs as an ordinary user. New Container warns about it and offers Use 8080 (8000 plus the port, or the next free one).
Fix
Use a port of 1024 or higher, such as 8080, and reach the service on that port (or through meshTerm's web forwarding). If you truly need port 80 or 443, you can lower the host's limit, but the caveat matters: it lets every user on the host bind those ports, not just this engine.
sudo sysctl -w net.ipv4.ip_unprivileged_port_start=80
echo 'net.ipv4.ip_unprivileged_port_start=80' | sudo tee /etc/sysctl.d/99-unprivileged-ports.conf
A reverse proxy that runs as root (or with a system engine) on 80 and 443, forwarding to your container on a high port, is the other common choice. A Mac's engine never gets this warning.
"Host Out of Disk Space": "HOST is out of disk space. Free some space (Disk Usage can remove unused images), then try again."
WhyImages, stopped containers, build cache and logs add up, and a pull or create needs room. Also a stack whose file could not be written ("can't write") usually means the disk is full there.
Fix
Tap Open Disk Usage and remove unused images, or on the host:
df -h # which filesystem is full?
docker system df # what the engine is using
docker image prune # dangling images only
docker image prune -a # every image no container uses (they can be pulled again)
Be careful with docker system prune --volumes: volumes hold your data, and it deletes unused ones. Check first.
"Image Not Built for This Host": "This image has no build for this host's processor (arm64). Pick a tag that's built for several processors."
WhyImages are built for a processor type. A common case is an amd64-only image on an Arm server (arm64, such as Oracle's or AWS Graviton's Arm instances, or an Apple silicon Mac), or the reverse.
Fix
uname -m # x86_64 means amd64; aarch64 or arm64 means arm64
Choose a tag or an image that publishes both (most official images do; look for "Architectures" on the registry page). If an image only exists for the other processor, run it somewhere that matches, or build it for your host.
"Couldn't Reach the Registry", "Registry Login Needed", "Registry Certificate Not Trusted", "Registry Is Limiting Requests"
WhyThe host (not your phone) downloads images, so the host needs internet, DNS and CA certificates, and some images need a login.
Fix
- "can't reach the image registry. Check the host's internet connection, DNS or proxy": test from the host. A
401 answer from the first line below still means the registry is reachable.
curl -sI https://registry-1.docker.io/v2/ | head -1
getent hosts registry-1.docker.io
- "This image needs a registry login." Sign in to the registry on the host, in a terminal (the app pre-fills the image's registry; use
sudo where that engine runs under sudo):
docker login # Docker Hub
docker login ghcr.io # another registry
podman login ghcr.io # or: nerdctl login, container registry login
- "The registry has no image by that name, or it's private." Check the name and tag, or log in.
- "Registry Is Limiting Requests" (Docker Hub's anonymous pull limit): try again in a few minutes, or log in.
- "Registry Certificate Not Trusted" ("usually because the host's CA certificates aren't installed"): minimal Debian images do not include them. A fresh server shows
x509: certificate signed by unknown authority.
sudo apt-get update && sudo apt-get install -y ca-certificates # Debian, Ubuntu
sudo dnf install -y ca-certificates # Fedora
sudo apk add ca-certificates # Alpine
If your network intercepts HTTPS with its own certificate, add that CA to the host's trust store and run sudo update-ca-certificates (Debian, Ubuntu).
"Image Name Needs a Registry" (Podman: "short-name ... did not resolve")
WhyDocker assumes Docker Hub for a bare name like nginx. Podman does not: on Debian 12 and Ubuntu 24.04 it fails with "short-name "nginx" did not resolve to an alias and no unqualified-search registries are defined in /etc/containers/registries.conf"; on Fedora some names work and others say "short-name resolution enforced but cannot prompt without a TTY". Fully qualified names work everywhere.
Fix
meshTerm writes a bare name as docker.io/library/NAME (or docker.io/USER/NAME) for Podman in New Container's form, preview and review, and never picks a registry by searching. When you run Podman yourself, type the full name:
podman pull docker.io/library/nginx
To make bare names work for your user, add unqualified-search-registries = ["docker.io"] to ~/.config/containers/registries.conf (or the system file /etc/containers/registries.conf). Check the registry's trust before you do; bare names can resolve to a different image than you expect.
"Rootless Docker stops when USER signs out. Keep it running with lingering."
WhyA rootless engine is a service of your user, and systemd stops a user's services when their last session ends, which includes your SSH session. The containers go with it. "Lingering" keeps the user's services running without a session. The Fleet and the host screen show this as an advisory line for a ready rootless engine.
Fix
sudo loginctl enable-linger $USER
loginctl show-user $USER -p Linger # Linger=yes
Tap Run in Terminal on the line, then Check Again.
Portainer: "Certificate Not Trusted Yet", "Portainer Environment Gone", "HTTPS Required", token errors
WhyPortainer servers are reached over HTTPS with an access token. Portainer makes its own certificate when it is installed, so "presented a certificate this device doesn't trust yet" is expected the first time.
Fix
- Certificate Not Trusted Yet: compare the fingerprint with the one your Portainer server shows before you trust it. Certificate Changed ("different from the one you trusted") can mean the server was reinstalled, or that someone is intercepting the connection; do not trust until you know which.
- "Portainer must use HTTPS (usually port 9443)." Use the
https:// address and Portainer's HTTPS port.
- "Portainer didn't accept this access token." Create a new one in Portainer under My account, then Access tokens, and replace it. A user with access to only the environments you want here is safest.
- Portainer Environment Gone / no Docker environment: the environment was removed on the server, or the token cannot use any Docker environment. Add one in Portainer under Environments, Add environment, then check again. Kubernetes environments are not supported, and neither is Portainer behind a URL path.
- Shell and Open in Terminal need a host added over SSH; a Portainer environment alone cannot open a terminal.
"Couldn't Reach the Web Page" / "The SSH server on HOST doesn't allow port forwarding (AllowTcpForwarding), so the web page can't be opened."
WhymeshTerm opens a container's web page through an SSH port forward, so the server must allow forwarding. A container that publishes its port only on the host's own address (127.0.0.1) is reached the same way: "Reach them with port forwarding."
Fix
Allow forwarding as in Port forwarding is blocked, and reload sshd for your system (the app's fix adds the right reload line):
sudo systemctl reload ssh # Debian, Ubuntu
sudo systemctl reload sshd # Fedora
sudo rc-service sshd reload # Alpine (OpenRC)