Coding from Anywhere with Claude Code and Qwen Code: My macOS Tahoe Setup with mosh, tmux, and Termius

If you live in Claude Code or Qwen Code and want to keep coding from your iPhone or iPad without sessions dying every time the train goes through a tunnel, this is the setup. It’s not glamorous, and yes, it’s a little unhealthy to have 24/7 access to your AI coding agents from your pocket — but it works, and once it’s running you stop thinking about it.

This guide walks through what I actually did on macOS Tahoe to get a rock-solid remote workflow: mosh on the Mac for connection resilience, tmux for session persistence, and Termius on iOS/iPadOS as the client. The result is that I can start a Claude Code session on my Mac at home, walk out the door, switch from Wi-Fi to cellular three times, and pick up exactly where I left off — same prompt, same context, same scrollback.

Why this combination

Plain SSH on a phone is painful because every network change drops the session. tmux alone helps because the session keeps running on the Mac, but reconnecting still requires re-authenticating and re-attaching every time the network blinks. mosh fixes the connection layer by using UDP and tolerating IP changes, so reconnects are nearly instant. tmux fixes the session layer by keeping your shell, your Claude Code process, and your scrollback alive on the Mac regardless of what the client is doing. Together they give you a remote terminal that genuinely behaves like it’s local.

What you need

A Mac running macOS Tahoe (this guide is what I did on Tahoe specifically, but it works on recent macOS versions in general), Homebrew installed, a router you can configure for port forwarding, and Termius installed on your iPhone and/or iPad. That’s it.

Step 1: install mosh and tmux on the Mac

Open Terminal on the Mac and run:

brew install mosh tmux

This installs both the mosh client and mosh-server (which is what listens for incoming connections), plus tmux.

Verify the install paths:

which mosh-server
which tmux

On Apple Silicon you’ll typically see /opt/homebrew/bin/mosh-server. Note the path — you’ll need it for the firewall step.

Step 2: enable SSH on the Mac

mosh bootstraps over SSH, so SSH must be on. Go to System Settings → General → Sharing → Remote Login and turn it on. Make sure your user account is allowed to log in.

Step 3: open the macOS firewall for mosh-server

By default, the macOS application firewall will silently block incoming UDP for mosh-server. The fix is to explicitly allow it:

sudo /usr/libexec/ApplicationFirewall/socketfilterfw --setglobalstate off
sudo /usr/libexec/ApplicationFirewall/socketfilterfw --add "$(which mosh-server)"
sudo /usr/libexec/ApplicationFirewall/socketfilterfw --unblockapp "$(which mosh-server)"
sudo /usr/libexec/ApplicationFirewall/socketfilterfw --setglobalstate on

This temporarily turns the firewall off, registers mosh-server as allowed, unblocks it, and turns the firewall back on. It only needs to be done once, and it survives reboots.

Step 4: make sure mosh-server is found over SSH

This is the step that trips most people up on macOS. When mosh connects, it logs in over SSH and tries to run mosh-server, but the SSH session’s PATH often doesn’t include /opt/homebrew/bin, so it fails with command not found: mosh-server.

The cleanest fix is to add Homebrew’s path to ~/.zshenv (not .zshrc), because .zshenv is read by non-interactive SSH shells too:

cat >> ~/.zshenv <<'EOF'
export PATH="/opt/homebrew/bin:$PATH"
export LC_ALL=en_US.UTF-8
export LANG=en_US.UTF-8
EOF

The LC_ALL and LANG lines matter because mosh-server refuses to start without a UTF-8 locale, and SSH sessions sometimes don’t have one set.

Step 5: forward UDP ports on your router

mosh uses UDP, by default in the range 60000–61000. You don’t need all 1000 ports — one port per concurrent session is enough, and 10 is plenty for personal use.

In your router’s admin panel, add a port-forwarding rule:

  • Protocol: UDP
  • External ports: 60001–60010
  • Internal IP: your Mac’s LAN IP
  • Internal ports: 60001–60010

You also need TCP 22 forwarded to the Mac for the SSH bootstrap. If you’d rather not expose SSH on the standard port, change it on the Mac and forward whatever port you choose.

When you connect, tell mosh to use this narrower range:

mosh --port=60001:60010 user@your-public-ip

Each concurrent mosh session uses one UDP port, so 10 ports = up to 10 simultaneous connections, which is way more than you’ll ever need from your own devices.

A note on NAT: mosh does not do NAT traversal. The server has to be reachable on those UDP ports somehow, which in a home-router setup means port forwarding. There are workarounds involving UPnP scripts or Tailscale, but plain port forwarding is the simplest reliable path.

Step 6: configure tmux to feel like a normal terminal

Out of the box, tmux feels a bit foreign. A small config file makes it behave much closer to a normal terminal — mouse scrolling works, copy/paste goes to the system clipboard, and the prefix is easier to reach. Create ~/.tmux.conf with:

# Mouse scrolling and pane selection
set -g mouse on

# True color support for modern apps like Claude Code
set -g default-terminal "screen-256color"
set -ga terminal-overrides ",xterm-256color:Tc"

# Faster key response and bigger scrollback
set -sg escape-time 0
set -g history-limit 100000

# Use Ctrl-a as prefix instead of Ctrl-b (easier to reach)
unbind C-b
set -g prefix C-a

# Windows and panes start at 1, not 0
set -g base-index 1
setw -g pane-base-index 1
set -g renumber-windows on

# Vim-style copy mode, with macOS clipboard integration
set -g mode-keys vi
bind-key -T copy-mode-vi v send -X begin-selection
bind-key -T copy-mode-vi y send-keys -X copy-pipe-and-cancel "pbcopy"

# Splits open in the current pane's directory
bind | split-window -h -c "#{pane_current_path}"
bind - split-window -v -c "#{pane_current_path}"

# Reload config with Ctrl-a r
bind r source-file ~/.tmux.conf \; display "Reloaded!"

# Clean status bar
set -g status-style bg=#2e3440,fg=#d8dee9

After creating the file, kill any existing tmux server so the new config takes effect cleanly:

tmux kill-server

Then start a fresh session — the config will load automatically.

Step 7: learn the handful of tmux keys you’ll actually use

With Ctrl-a as the prefix, the small set of commands worth knowing:

To copy text, press Ctrl-a [ to enter copy mode, navigate with arrow keys or vim-style h/j/k/l, press v to start selecting, move to the end of the selection, then press y to copy. The text goes straight to your Mac’s clipboard via pbcopy, so you can paste it anywhere with Cmd-V.

To paste inside tmux, press Ctrl-a ].

To detach from a session (leaving it running), press Ctrl-a d. To split panes, Ctrl-a | for vertical and Ctrl-a – for horizontal — both open in the current directory thanks to the config above. To switch panes, Ctrl-a then arrow keys. To reload the config after editing, Ctrl-a r.

If mouse mode is on, you can also just scroll with the trackpad or wheel, click panes to switch focus, and drag pane borders to resize. It really does feel like a normal terminal.

Step 8: start a named tmux session on the Mac

This is where you’ll actually run Claude Code or Qwen Code. Pick a name per project so you can have multiple sessions running independently:

tmux new -s claude1 -c ~/path/to/your/project

The -s claude1 names the session, and -c sets its starting directory. Inside the session, just run claude (or qwen, or whatever) as you normally would.

When you’re done for now, detach with Ctrl-a d. The session keeps running on the Mac. You can close Terminal entirely — tmux runs as a background server independent of any terminal window, so closing windows or quitting Terminal.app does not kill your sessions. Only a reboot or an explicit tmux kill-server will.

To see what’s running:

tmux ls

To reattach later from the Mac itself:

tmux attach -t claude1

Step 9: set up Termius on iOS and iPadOS

Install Termius from the App Store. It’s free for personal use and has the cleanest mobile experience I’ve found, with built-in SSH key generation protected by Face ID.

In Termius, generate a new SSH key (Settings → Keychain → New Key). Termius will store the private key in the Secure Enclave behind Face ID, which means even if your phone is unlocked the key can’t be used without your face. Then upload the public key to your Mac — Termius has a one-tap “upload key to server” feature once you’ve added the host with password auth, which appends the public key to ~/.ssh/authorized_keys on the Mac and switches future connections to key auth.

Add a host in Termius with:

  • Hostname: your public IP or dynamic DNS name
  • Port: 22 (or whatever you forwarded)
  • Username: your macOS username
  • Key: the Termius-managed key you just uploaded

Then enable mosh for that host: in the host’s settings, turn on Mosh and set the port range to 60001:60010 to match what you forwarded on the router.

Finally, in the host’s Startup Snippet (advanced settings), add:

tmux attach -t claude1 || tmux new -s claude1 -c ~/path/to/your/project

This runs automatically every time you connect: it tries to reattach to the existing claude1 session, and if it doesn’t exist yet, creates a new one in the right directory. One tap and you’re back exactly where you left off.

If you have multiple projects, set up one host entry per session — claude1qwen-experimentsside-project, whatever — each with its own startup snippet pointing at its own session name and directory.

Step 10: use it

Tap the host in Termius. mosh connects, the startup snippet fires, tmux reattaches, and Claude Code is sitting there exactly as you left it — mid-prompt, mid-output, scrollback intact. Walk into an elevator, lose signal, come out the other side: the session resumes within a second or two without you doing anything. Close the app, open it tomorrow: same session, still there.

Connecting from a remote Mac

If you also want to connect from another Mac (a work laptop, say), the equivalent is just:

mosh --port=60001:60010 user@your-public-ip -- tmux attach -t claude1

Or if you’re inside iTerm2 and want native tabs that mirror your tmux windows, use iTerm2’s tmux integration:

ssh user@your-mac "tmux -CC attach -t claude1"

iTerm2 will translate tmux windows into real iTerm2 tabs, with native copy/paste and mouse handling — the cleanest way to use a remote tmux session on a Mac.

Cleaning up

When you’re truly done with a session, kill it from inside or out:

tmux kill-session -t claude1

Or to nuke everything:

tmux kill-server

A few things that bit me

If you change ~/.tmux.conf and the changes don’t seem to apply, that’s because tmux only reads the config when the server starts. Either reload it with Ctrl-a r, or run tmux kill-server and start fresh.

If mosh connects but you get command not found: mosh-server, your ~/.zshenv is missing the Homebrew path — go back to step 4. If mosh connects but hangs at Nothing received from server on UDP port 60001, your firewall or router isn’t actually forwarding UDP — go back to steps 3 and 5 and verify with lsof -i UDP:60001-60010 on the Mac while a connection attempt is in flight.

If your shell PID changes between reconnects, you’re not actually reattaching — you’re starting a new session each time. Check that the Termius startup snippet uses tmux attach -t claude1 first and only falls back to tmux new if that fails.

Final thought

Is it healthy to have Claude Code one tap away from your phone at all times? Probably not. But the same setup is genuinely useful when something breaks while you’re out, when you want to kick off a long agent run from bed, or when you just want to read what Claude was up to overnight. The combination of mosh’s network resilience and tmux’s session persistence is what makes it actually pleasant rather than fiddly — and once it’s set up, it stays out of your way.