Skip to content

Instantly share code, notes, and snippets.

@Git-on-my-level
Created January 28, 2026 10:38
Show Gist options
  • Select an option

  • Save Git-on-my-level/bc83166d5e56caaaa1b74427bebbd92c to your computer and use it in GitHub Desktop.

Select an option

Save Git-on-my-level/bc83166d5e56caaaa1b74427bebbd92c to your computer and use it in GitHub Desktop.
Set up a new Mac to with SSH/Mosh, Tailscale, and minimal sane tmux setup

Mac-to-Mac Remote Shell Stack (Tailscale + SSH + Mosh + tmux) — Low-Lag, High-Reliability Setup

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-server via a non-interactive SSH command; Homebrew lives in /opt/homebrew/bin on Apple Silicon. If PATH is not made visible to non-interactive shells, Mosh will fail with command not found: mosh-server even though which mosh-server works interactively.


Variables (fill these in)

  • REMOTE_USER — user on the server (e.g., alice)
  • REMOTE_HOST — remote hostname (prefer .ts.net) or Tailscale IP (e.g., server.tail123.ts.net or 100.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=dazheng
  • REMOTE_HOST=mac-mini.tailXXXX.ts.net
  • REMOTE_TS_IP=100.98.67.58

0) Prereqs

  • macOS on both client and server
  • Homebrew installed on both
  • Tailscale account + devices enrolled

1) Install and bring up Tailscale (client + server)

On both machines:

brew install tailscale
sudo tailscale up

From client, confirm reachability:

tailscale status
tailscale ping REMOTE_HOST_OR_TS_IP

Expected: pong ... in ms

  1. 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.

  1. Install Mosh (client + server)

On both machines:

brew install mosh
mosh --version

  1. 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.

  1. 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.

  1. 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

  1. 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

  1. 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”.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment