Original tool
Is your host ready for a phone terminal?
Nine checks for the failures that only show up when you leave the desk. The snippet runs on your machine. No host data is sent anywhere. This page has no collector and no form post; the script's output never leaves your terminal.
#!/usr/bin/env bash
# SSHHIP host-check - runs entirely on your machine.
# Nothing is uploaded. Review before running.
set -u
ok() { printf 'OK %s %s\n' "$1" "${2-}"; }
warn() { printf 'WARN %s %s\n' "$1" "${2-}"; }
fail() { printf 'FAIL %s %s\n' "$1" "${2-}"; }
echo "SSHHIP host-check"
echo "All evaluation is local. No data leaves this machine."
echo
# SSHD
if command -v systemctl >/dev/null 2>&1 && systemctl is-active --quiet sshd 2>/dev/null; then
ok SSHD "sshd active (systemd)"
elif command -v systemctl >/dev/null 2>&1 && systemctl is-active --quiet ssh 2>/dev/null; then
ok SSHD "ssh.service active (systemd)"
elif launchctl print system/com.openssh.sshd >/dev/null 2>&1; then
ok SSHD "macOS Remote Login service present"
else
warn SSHD "could not confirm sshd; check Remote Login / sshd manually"
fi
# SHELL
shell_name="$(basename "${SHELL:-/bin/sh}")"
ok SHELL "login shell=$shell_name"
# Set by multiplexer_path_check so later checks can tell "not installed" from
# "installed somewhere this probe did not look".
interactive_available=0
multiplexer_path_check() {
local label="$1"
local available=0
local found=""
local bin
for bin in tmux herdr; do
if command -v "$bin" >/dev/null 2>&1; then
available=$((available + 1))
found="$found $bin=$(command -v "$bin")"
else
found="$found $bin=MISSING"
fi
done
interactive_available="$available"
if [ "$available" -gt 0 ]; then
ok "$label" "$found"
else
fail "$label" "$found"
fi
}
# PATH-I interactive baseline (this shell)
multiplexer_path_check PATH-I
# PATH-N non-interactive ssh to self if possible.
# Not being able to ssh to yourself is a different result from ssh working and
# seeing nothing - only the second one is the PATH trap this check exists for.
if command -v ssh >/dev/null 2>&1; then
remote="$(ssh -o BatchMode=yes -o ConnectTimeout=3 localhost 'command -v tmux; command -v herdr; true' 2>/dev/null)"
ssh_status=$?
remote="$(printf '%s' "$remote" | tr '\n' ' ')"
if [ "$ssh_status" -ne 0 ]; then
warn PATH-N "could not ssh to localhost in BatchMode, so this check could not run; try it from the machine you connect with"
elif [ -n "$remote" ]; then
ok PATH-N "localhost non-interactive sees:$remote"
else
fail PATH-N "ssh to localhost worked but saw no tmux/herdr - this is the PATH trap; see PATH-FB"
fi
else
warn PATH-N "ssh client missing; skipped"
fi
# PATH-FB common prefixes. Only interesting when discovery already failed:
# binaries installed outside these three directories are perfectly normal.
fb=""
for dir in /opt/homebrew/bin /usr/local/bin "$HOME/.local/bin"; do
for bin in tmux herdr; do
if [ -x "$dir/$bin" ]; then
fb="$fb $dir/$bin"
fi
done
done
if [ -n "$fb" ]; then
ok PATH-FB "$fb"
elif [ "$interactive_available" -gt 0 ]; then
ok PATH-FB "none in the common prefixes, but PATH-I already found your binaries elsewhere - nothing to fix"
else
warn PATH-FB "no tmux/herdr binaries in common prefix paths"
fi
# MUX versions
if command -v tmux >/dev/null 2>&1; then
ok MUX "tmux $(tmux -V 2>/dev/null | tr '\n' ' ')"
elif [ -x /opt/homebrew/bin/tmux ]; then
ok MUX "/opt/homebrew/bin/tmux $(/opt/homebrew/bin/tmux -V 2>/dev/null)"
else
warn MUX "tmux not visible in this shell"
fi
if command -v herdr >/dev/null 2>&1; then
ok MUX "herdr $(herdr --version 2>/dev/null | head -1 | tr '\n' ' ')"
elif [ -x /opt/homebrew/bin/herdr ]; then
ok MUX "/opt/homebrew/bin/herdr present"
else
warn MUX "herdr not visible in this shell"
fi
# KEEP
if [ -f /etc/ssh/sshd_config ] || ls /etc/ssh/sshd_config.d/*.conf >/dev/null 2>&1; then
keep="$(grep -hE '^(ClientAliveInterval|ClientAliveCountMax)' /etc/ssh/sshd_config /etc/ssh/sshd_config.d/*.conf 2>/dev/null | tr '\n' ' ')"
if [ -n "$keep" ]; then
ok KEEP "$keep"
else
warn KEEP "no ClientAlive* settings found; phone sleep may drop idle sessions"
fi
else
warn KEEP "sshd_config not readable"
fi
# AUTH methods offered on localhost if possible
if command -v ssh >/dev/null 2>&1; then
auth="$(ssh -o BatchMode=yes -o PreferredAuthentications=none -o ConnectTimeout=3 localhost true 2>&1 | tr '\n' ' ' || true)"
if printf '%s' "$auth" | grep -qi 'permission denied'; then
ok AUTH "ssh negotiates (permission denied expected without creds)"
else
warn AUTH "could not probe auth methods cleanly"
fi
else
warn AUTH "ssh client missing; skipped"
fi
# SLEEP (macOS)
if command -v pmset >/dev/null 2>&1; then
sleep_lines="$(pmset -g custom 2>/dev/null | grep -E 'sleep|disablesleep|tcpkeepalive' | tr '\n' '; ')"
if [ -n "$sleep_lines" ]; then
ok SLEEP "$sleep_lines"
else
warn SLEEP "pmset present but no sleep lines parsed"
fi
else
warn SLEEP "not macOS pmset; check host sleep policy manually"
fi
echo
echo "Done. Interpret codes on https://sshhip.com/tools/host-check/"
Review the script, then run it on the host: bash host-check.sh
What each code means
SSHD - Is sshd / Remote Login present?
Enable Remote Login on macOS or ensure sshd is installed and reachable on Linux.
SHELL - Which login shell will profile files come from?
zsh uses .zprofile/.zshrc; bash uses .bash_profile/.profile; csh/tcsh differ. PATH fixes must match the shell.
PATH-I - Can this interactive shell see tmux/herdr?
If this fails, install the multiplexer or fix your interactive PATH first.
PATH-N - Can non-interactive SSH see tmux/herdr?
The classic Apple Silicon Homebrew trap. FAIL means ssh connected and still saw nothing: login-shell probes plus /opt/homebrew/bin fallbacks are what fix that false not-installed result. WARN means the script could not ssh to localhost at all, so this check did not run - re-run it from the machine you actually connect with.
PATH-FB - Are binaries present in common prefix directories?
Only interesting when discovery already failed. If PATH-N fails but PATH-FB finds /opt/homebrew/bin/tmux, the binary exists and discovery is the bug. Binaries installed outside those three directories are normal and are not a problem on their own.
MUX - What versions are visible?
Very old tmux builds may lack format features; herdr is moving quickly - note the version you run.
KEEP - Will idle sessions survive phone sleep?
Consider ClientAliveInterval / ClientAliveCountMax on the server so half-open phone connections clear cleanly.
AUTH - Can SSH negotiate at all?
For Tailscale SSH keyless flows, the client must be willing to offer none/publickey before insisting on a password prompt.
SLEEP - Will the host itself sleep under an agent?
A perfect phone client cannot wake a Mac that powered down. Align pmset / power settings with the workload.
The two failure stories
PATH-N is why phone clients silently decide your multiplexer is missing on a healthy Homebrew Mac. SLEEP is why your agent "stopped" overnight when the client did nothing wrong. Both are host problems with host fixes.
What this does not check
- Firewall / tailnet ACL policy beyond a best-effort localhost probe
- Whether your agent binary itself is installed
- Disk encryption, MDM, or corporate SSH middleboxes
- SSHHIP app configuration on the phone