This gist documents a reproducible setup for a “remote shell that feels local” between a laptop and one or more headless Macs (e.g., Mac minis). It is optimized for:
- High latency / packet loss links (SSH typing lag, key repeats, “rubber band” echo)
- Roaming networks (Wi-Fi drops, IP changes)
- Long-lived sessions (detach/reattach, crash tolerance)
Core design decisions:
- Tailscale provides a private, encrypted, stable network plane (no port forwarding/NAT complexity).
- OpenSSH (Remote Login) is used for authentication, key management, and compatibility.
- Mosh replaces interactive SSH for terminals under bad networks (local echo + UDP-based transport).
- tmux provides durable sessions and workflow structure; paired with resurrection plugins for convenience.
AI-agent note: Many of these steps exist because macOS shells behave differently depending on login vs interactive vs non-interactive execution. Mosh invokes
mosh-servervia a non-interactive SSH command; Homebrew lives in/opt/homebrew/binon Apple Silicon. If PATH is not made visible to non-interactive shells, Mosh will fail withcommand not found: mosh-servereven thoughwhich mosh-serverworks interactively.
REMOTE_USER— user on the server (e.g.,alice)REMOTE_HOST— remote hostname (prefer.ts.net) or Tailscale IP (e.g.,server.tail123.ts.netor100.x.y.z)REMOTE_TS_IP— remote Tailscale IP (100.x.y.z)CLIENT— your laptop (macOS assumed)SERVER— your Mac mini or other macOS host (macOS assumed)
Examples:
REMOTE_USER=dazhengREMOTE_HOST=mac-mini.tailXXXX.ts.netREMOTE_TS_IP=100.98.67.58
- macOS on both client and server
- Homebrew installed on both
- Tailscale account + devices enrolled
On both machines:
brew install tailscale
sudo tailscale upFrom client, confirm reachability:
tailscale status
tailscale ping REMOTE_HOST_OR_TS_IP
Expected: pong ... in ms
⸻
- Enable “real SSH” on the server (macOS Remote Login)
Mosh requires standard SSH to start mosh-server. Tailscale SSH (tailscale ssh) is not a drop-in replacement.
On the server: • System Settings → General → Sharing → Remote Login → ON • Allow access to REMOTE_USER
From the client:
ssh REMOTE_USER@REMOTE_HOST
Expected: password prompt and login works.
⸻
- Install Mosh (client + server)
On both machines:
brew install mosh
mosh --version
⸻
- Fix macOS PATH so Mosh can find mosh-server (server)
Why this exists (AI-agent context) • Mosh runs: ssh user@host mosh-server ... • This invokes a non-interactive, non-login shell. • On macOS with zsh, many PATH configs are not loaded for that shell type. • Resulting symptom: zsh:1: command not found: mosh-server
The fix
On the server (Apple Silicon Homebrew path shown):
sudo nano /etc/zshenv
Add at the top:
export PATH="/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin"
Verify from the client (critical):
ssh REMOTE_USER@REMOTE_HOST 'which mosh-server'
Expected:
/opt/homebrew/bin/mosh-server
Now test Mosh from client:
mosh REMOTE_USER@REMOTE_HOST
Expected: immediate shell, lag-free typing.
Note: If you are on Intel Homebrew, adjust /opt/homebrew/bin → /usr/local/bin accordingly.
⸻
- Passwordless SSH (client → server)
Generate a key (client) if needed
ls ~/.ssh/id_ed25519.pub || ssh-keygen -t ed25519 -C "client"
Copy the key to server
Preferred:
ssh-copy-id REMOTE_USER@REMOTE_HOST
If unavailable:
cat ~/.ssh/id_ed25519.pub | ssh REMOTE_USER@REMOTE_HOST \
'mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 700 ~/.ssh && chmod 600 ~/.ssh/authorized_keys'
Verify:
ssh REMOTE_USER@REMOTE_HOST
mosh REMOTE_USER@REMOTE_HOST
Expected: no password prompts.
⸻
- Harden SSH on the server
Goals • Disable passwords and keyboard-interactive prompts (reduces brute force + credential stuffing risk). • Allow only intended users. • Optionally bind SSH to Tailscale only (removes LAN exposure).
Edit server config:
sudo nano /etc/ssh/sshd_config
Add/ensure:
# Core: key-only auth
PubkeyAuthentication yes
PasswordAuthentication no
KbdInteractiveAuthentication no
# Restrict access
AllowUsers REMOTE_USER
PermitRootLogin no
# Limit auth abuse surface
MaxAuthTries 3
LoginGraceTime 20
Optional: bind SSH to Tailscale only (recommended if you rely on Tailscale)
This prevents SSH exposure on LAN interfaces.
ListenAddress REMOTE_TS_IP
Validate:
sudo sshd -t
Reload SSH without dropping your current connection:
sudo launchctl kickstart -k system/com.openssh.sshd
Verify effective config:
sudo sshd -T | grep -E 'passwordauthentication|kbdinteractiveauthentication|pubkeyauthentication|listenaddress|allowusers'
Expected includes:
pubkeyauthentication yes
passwordauthentication no
kbdinteractiveauthentication no
⸻
- tmux: durable sessions + optional restore
Install on server:
brew install tmux
(Optional) Install TPM plugin manager + resurrection:
git clone https://github.com/tmux-plugins/tpm ~/.tmux/plugins/tpm
Create ~/.tmux.conf on server:
# Mouse support
set -g mouse on
# Splits/windows start in current directory
bind % split-window -h -c "#{pane_current_path}"
bind '"' split-window -v -c "#{pane_current_path}"
bind c new-window -c "#{pane_current_path}"
# QoL
set -s escape-time 0
set -g renumber-windows on
set -g history-limit 100000
# Pane sync toggle
bind y setw synchronize-panes
# Plugins
set -g @plugin 'tmux-plugins/tpm'
set -g @plugin 'tmux-plugins/tmux-resurrect'
set -g @plugin 'tmux-plugins/tmux-continuum'
set -g @continuum-restore 'on'
set -g @continuum-save-interval '15'
set -g @resurrect-capture-pane-contents 'on'
set -g @resurrect-strategy-nvim 'session'
run '~/.tmux/plugins/tpm/tpm'
Start tmux:
tmux
Inside tmux, install plugins: • Press Ctrl-b then I
Recommended workflow:
mosh REMOTE_USER@REMOTE_HOST
tmux new -s main
⸻
- Notes / gotchas
“Mosh doesn’t support port forwarding”
Correct. Use: • SSH for tunnels/port-forwarding • Mosh for interactive shell responsiveness
“Mosh still prompts for password”
That means SSH keys aren’t installed or not being used. Verify: • ssh REMOTE_USER@REMOTE_HOST is passwordless • ~/.ssh/authorized_keys exists on server with correct perms • ~/.ssh/id_ed25519 exists on client and is readable
“Mosh can’t find mosh-server but which mosh-server works”
Almost always PATH is only set for login/interactive shells. Fix is /etc/zshenv (loaded for all zsh invocations).
⸻
One-command smoke test (client)
ssh REMOTE_USER@REMOTE_HOST 'which mosh-server && sudo sshd -T | grep -E "passwordauthentication|kbdinteractiveauthentication|pubkeyauthentication"'
mosh REMOTE_USER@REMOTE_HOST
⸻
Rationale summary (for future you and AI agents) • Mosh solves terminal interactivity on bad networks via local echo + UDP transport. SSH over TCP is often miserable for typing when RTT/packet loss is high. • Tailscale makes Mosh operationally simple: no public IPs, no UDP port wrangling, stable addressing. • macOS zsh PATH is the main footgun: non-interactive shells omit Homebrew paths; Mosh triggers exactly that environment. /etc/zshenv makes Homebrew available universally. • tmux makes sessions durable and composable; Mosh makes the interactive experience good; together they provide “remote feels local”.
⸻