Claude Code in My Pocket: Remote Access, Built by Hand

From an iPhone to Claude Code on a MacBook through Tailscale, hardened OpenSSH, mosh and tmux — set up one step at a time, gotchas included.

tau is a personal agent platform I am building for myself: a hub running on my MacBook that I talk to from Telegram, from a terminal and by voice. I am building it layer by layer, and the bottom one, layer zero, has nothing to do with the agent at all. Its only job is letting me use Claude Code — Anthropic's coding agent that runs in the terminal — on the Mac from my phone, so the work does not stop when I leave the desk.

The repo already had scripts that set this up, and the part that needs no sudo, like tmux and mosh, was already installed. I still did the rest, everything that needs a password, an account or the phone, by hand and one step at a time instead of running the script: I cannot defend a door I set up without understanding it. Most of what I learned came from the gotchas along the way.

The chain

Termius on the iPhone → Tailscale → OpenSSH on the Mac → mosh → tmux → Claude Code. Each link has one job.

from the phone to claude code
iPhoneMacBooktailnet · TailscaleSSH · keymosh · UDPstartstau-attachrejectedTermiusSSH clientsshdRemote Login · :22mosh-serverUDP sessiontmuxsession: tauClaude CodeclaudeSomeone at a caféoff the tailnetSession2Gate1Client2Agent1

The road: Tailscale

Tailscale is a VPN that connects your devices as if they were on the same local network, wherever they actually are; that private network is called a tailnet. No ports need opening on the router. I installed it on the Mac with brew install --cask tailscale-app, allowed the system extension under Privacy & Security, and logged in. Then I installed it on the iPhone and logged in with the same account.

The first confusion came right away: I thought I had opened Tailscale, and I had opened Termius. Termius is the SSH client I use on the iPhone; SSH is the standard way to open a secure terminal on another machine. The analogy that separated the two in my head: Tailscale is the road, Termius is the car. Tailscale lays a road between the phone and the Mac, but nothing travels on it by itself; the thing that drives to the Mac is Termius.

To see from the Mac that the road really exists:

tailscale ping <phone>

The answer came back as via <lan-ip>:41641: the two devices found each other on the same Wi-Fi, and the traffic goes directly, peer to peer. Had it said via DERP(...), the traffic would have been going through DERP, Tailscale's relay servers. Later I tried it over 5G, and it worked there too.

The key, and the command on the clipboard

For SSH I use a key pair instead of a password: the private key stays on the phone, the public key goes to the Mac, and the Mac only lets in whoever holds that private key. In Termius I generated an ED25519 key under Keychain → Generate Key and added the public key to ~/.ssh/authorized_keys on the Mac:

mkdir -p ~/.ssh && chmod 700 ~/.ssh
echo "$(pbpaste)" >> ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys

Then I checked:

ssh-keygen -l -f ~/.ssh/authorized_keys
# ... is not a public key file.

When I looked with cat, the file did not contain the key; it contained the command itself.

echo 'ssh-ed25519 AAAA… iphone-termius' > ~/.ssh/authorized_keys

A single >, so the broken line goes too. The trailing iphone-termius is a comment: SSH does not care about it, but if the phone gets lost it shows at a glance which line to delete. This time ssh-keygen -l printed the key's fingerprint (SHA256:…).

Lock the door before opening it

On macOS the SSH server, OpenSSH's sshd service, is called Remote Login in System Settings. I hardened it before turning it on; I did not want it open with default settings for even a minute.

/etc/ssh/sshd_config contains the line Include /etc/ssh/sshd_config.d/*: the files in that folder are read in alphabetical order, and for most options the first value seen wins. macOS puts its own settings in 100-macos.conf. That is why mine is 010-tau.conf: it is read first, so it gets the first word.

I rendered the file from the repo's template with sed and put it in place with install:

sed -e 's|__USER__|<user>|g' \
    -e 's|__TAILNET_V4__|100.64.0.0/10|g' \
    -e 's|__TAILNET_V6__|fd7a:115c:a1e0::/48|g' \
    infra/remote/sshd/tau.conf > 010-tau.conf
sudo install -m 644 -o root -g wheel 010-tau.conf /etc/ssh/sshd_config.d/010-tau.conf

install, because sshd refuses a config file that is group- or world-writable, and install sets the owner and the permissions right in one step. The heart of it is these lines:

PasswordAuthentication no
KbdInteractiveAuthentication no
AuthenticationMethods publickey
PermitRootLogin no
AllowUsers <user>@100.64.0.0/10 <user>@fd7a:115c:a1e0::/48 <user>@127.0.0.1 <user>@::1
ClientAliveInterval 30
ClientAliveCountMax 4
AllowAgentForwarding no
AllowTcpForwarding local

"no hostkeys available"

After installing it, and before turning Remote Login on, I tested it:

sudo sshd -t
# sshd: no hostkeys available -- exiting.

The key here is not my key. In SSH both sides prove who they are. The user key lets me tell the Mac "this is me"; that is the one I generated in Termius. The host key is the server's identity: it proves to me that the thing I connected to really is my Mac. Remote Login had never been turned on on this Mac, and since macOS normally creates the host keys on the first connection, there were none at all.

sudo ssh-keygen -A

-A only generates the host keys that are missing and leaves existing ones alone. After that, sshd -t passed silently. The repo's setup script had the same gap, so I fixed that too.

The host key is useful for one more thing: on the first connection Termius shows its fingerprint and asks whether you trust it. Instead of saying "yes" blindly, compare it with the value on the Mac; if the two match, the machine you connected to really is that Mac:

ssh-keygen -l -f /etc/ssh/ssh_host_ed25519_key.pub

Turning Remote Login on

sudo systemsetup -setremotelogin on
# ... requires Full Disk Access privileges.

Giving the terminal Full Disk Access means opening the whole disk to everything that runs in that terminal; too much for a single setting. I turned Remote Login on by hand in System Settings → General → Sharing.

Two more doors left open

The real surprise was on that Sharing screen. Next to Remote Login, two more things were on:

netstat -an -p tcp | grep LISTEN

These ports were listening on *, meaning every interface, café Wi-Fi included. On top of that they accept the macOS login password, the hardening I had just done does not cover them at all (it is sshd's config only), and the firewall was off, because mosh runs over UDP. So anyone on the same café Wi-Fi could have tried passwords against these services. I turned both off; of the remote access doors, only 22 is left.

Health check

doctor.sh is the repo's health check; it probes the whole chain without changing anything. My favourite check: it tries to log in to its own machine with a password, and only believes that passwords are really off if the server says nothing but Permission denied (publickey). Writing "no" in a config and the server actually refusing are not the same thing. Everything came up green.

The car: Termius and mosh

A new host in Termius:

mosh (mobile shell) uses SSH only to authenticate and to start mosh-server on the Mac; after that the session flows over UDP. Because it is not tied to a single TCP connection, switching from Wi-Fi to 5G or the phone going to sleep does not kill it; when the phone wakes up, the screen carries on where it left off.

tmux: the session lives on the Mac

mosh makes the connection resilient, but if I close the connection completely the terminal goes with it, and so does the Claude Code running inside. tmux is a terminal session that lives on the Mac independently of the connection: when I drop off, whatever runs inside keeps running, and when I reconnect I am back on the same screen.

The repo's tau-attach is essentially one line — attach to the tmux session named tau, or create it:

exec tmux new-session -A -s tau

I typed it by hand from the phone first, and tmux's status bar appeared at the bottom of the screen. Then I put tau-attach in the host's Startup Snippet field in Termius; now every connection lands straight in the running session.

The test

I connected over 5G and typed claude. It did not ask me to log in again. (In case the keychain, the macOS password vault, looks locked in an SSH session and Claude Code asks to log in, the README documents a long-lived token route with claude setup-token; I did not need it.)

The real test: lock the phone and wait more than two minutes, longer than it takes sshd to clean up dead connections. When I came back, the same screen. Then I closed the connection completely and reconnected: still the same, still-running Claude session.

The one gap: sleep

When the lid closes the Mac goes to sleep, and nothing can reach a sleeping Mac. For now a launchd agent runs caffeinate -s: caffeinate is the macOS command that prevents sleep, and with -s it only applies while on the charger; on battery and with the lid closed the Mac still sleeps. Later, TauBar, the tray app that will live in tau's menu bar, will take this job over.

Security model

How to verify

  1. tailscale ping <phone> answers: via <ip>:<port> means a direct connection, via DERP(...) a relayed one.
  2. ssh-keygen -l -f ~/.ssh/authorized_keys prints a fingerprint for every line and does not say "is not a public key file".
  3. sudo sshd -t returns without printing anything.
  4. ssh -o BatchMode=yes -o PubkeyAuthentication=no <user>@127.0.0.1 true says only Permission denied (publickey).
  5. netstat -an -p tcp | grep LISTEN shows no 5900, 3283 or 3031.
  6. The host key fingerprint Termius shows on the first connection matches the output of ssh-keygen -l -f /etc/ssh/ssh_host_ed25519_key.pub.
  7. Connect from the phone, start claude, lock the phone for more than two minutes: same screen. Close the connection completely and open it again: same session.

That is layer zero. I can now work with Claude Code when I am away from the Mac; next up is the agent itself.

If you want to set it up yourself: Claude Code from Your Phone: A From-Scratch Setup Guide.