← All posts

Running Claude Code on a Remote Box Over SSH (2026)

The agent moved to a server and everything you configured is on the wrong machine. Keepalives, truecolor over the wire, and the notification problem that has no clean local answer.

At some point the laptop stops being the right place to run the agent. The repo needs 32GB to build, or the test suite needs a real database, or the model needs to sit next to the data instead of pulling it across your home connection.

So you SSH into a box and run Claude Code there. And then you discover that everything you set up locally — the notifications, the themes, the session management — is on the wrong machine.

Here is the setup that actually holds up, and the specific things that break if you skip a step.

Get the connection stable first

Everything downstream depends on this, and most people's SSH config is whatever they had in 2019.

# ~/.ssh/config
Host devbox
  HostName 10.0.4.22
  User peter
  ForwardAgent yes
  ServerAliveInterval 30
  ServerAliveCountMax 6
  TCPKeepAlive no
  ControlMaster auto
  ControlPath ~/.ssh/cm-%r@%h:%p
  ControlPersist 10m

The lines that matter:

ServerAliveInterval 30 with ServerAliveCountMax 6 gives you three minutes of network trouble before the client gives up. Without it, a laptop that changes wifi networks drops the session immediately, and an agent mid-edit dies with it.

ControlMaster and ControlPersist multiplex additional connections over the first one. Second and third SSH sessions to the same host open instantly instead of doing a full handshake. If you open several sessions to the same box — and you will — this is the difference between a two second wait and none.

ForwardAgent yes lets the agent use your local SSH keys for git operations on the remote box, so you are not copying a deploy key onto a shared machine. Only do this for hosts you control.

Now make it survive

This is the non-negotiable part, and it is where the local-only advice stops applying.

Locally, I have argued that a multiplexer is often unnecessary. Remotely it is mandatory. Your connection will drop — the lid closes, the train enters a tunnel, the VPN reconnects — and without a multiplexer on the far side, every process you started dies with the shell.

ssh devbox
tmux new -s agent
claude

Detach with Ctrl-b d, close the laptop, come back later:

ssh devbox
tmux attach -t agent

The agent kept working the whole time.

A small quality-of-life thing worth adding, so you never have to remember which session name you used:

# add to ~/.ssh/config under the host
# RemoteCommand tmux new -A -s agent
# RequestTTY yes

tmux new -A -s agent attaches if the session exists and creates it if not, so one command always does the right thing. Leave those commented until you are sure, because a broken RemoteCommand makes plain ssh devbox unusable for file copies and git.

If you prefer Zellij, it has the same story with better ergonomics for layouts — Zellij vs tmux for AI agents goes through the tradeoff. On a small VPS, tmux's lower memory footprint is a real argument.

Our own tmux power user setup covers the config side in more depth.

Fix the colors before you do anything else

Your carefully chosen theme is a local thing. The remote box knows nothing about it, and the negotiation between the two is where truecolor quietly dies.

On the remote machine:

# ~/.tmux.conf
set -g default-terminal "tmux-256color"
set -ga terminal-overrides ",*256col*:Tc"

Then verify rather than assume:

echo $COLORTERM        # want: truecolor
tmux info | grep Tc    # want: (flag) Tc: true

If $COLORTERM is empty on the remote side, your SSH session is not passing it. Either add SendEnv COLORTERM locally with a matching AcceptEnv in the server's sshd_config, or just set it in the remote shell profile — simpler, and it is not a secret:

# remote ~/.bashrc or ~/.zshrc
export COLORTERM=truecolor

Skip this and every theme you use on the remote box renders in 256 colors. It will look almost right, which is worse than looking broken, because you will spend a month vaguely thinking your theme got uglier.

While you are there, fonts are still local — the remote box has no say. If box-drawing characters or icons are wrong, that is your local font, covered in Nerd Fonts for terminal.

The notification problem, which is the real one

Everything above is solvable with config. This one is structural.

Agent notifications generally work by watching a local process — a hook fires, or the terminal watches the process tree of the session and notices when output stops and a prompt appears. Over SSH, none of that is local. The process is on another machine, wrapped in a multiplexer, arriving at your terminal as an undifferentiated stream of bytes.

So the usual approaches degrade:

Claude Code hooks run on the remote box. A hook that calls osascript or notify-send will fire into the void on a headless server. See the hooks guide for how they are wired.

Terminal bell actually does work, because \a travels down the wire like any other byte. This makes it the most reliable remote notification, and it is one line in a hook:

{
  "hooks": {
    "Stop": [{ "hooks": [{ "type": "command", "command": "printf '\\a'" }] }]
  }
}

Then make sure the layers between you and it do not eat it. tmux needs set -g bell-action any, and your terminal needs its bell enabled — visual or audible, but not "ignore."

tmux monitoring is the other option, and it is noisier than it sounds: monitor-activity fires on any output, and an agent that redraws a spinner is producing output constantly. monitor-silence is the better fit for agents — it alerts on the absence of output, which is exactly what "waiting for you" looks like:

# in the pane running the agent
tmux set-window-option monitor-silence 15

Fifteen seconds of silence and the window flags itself. That is the closest thing to a working remote agent alert that does not require anything installed on the far side.

MOLTamp's session monitoring reads the local side of the stream, so an SSH session behaves like any other — the tab lights amber when the pane goes quiet waiting on input and green when a turn finishes, and ⌘P finds the session by name when you have a dozen open. It does not need anything installed on the remote box, which is the point. More on how that works in the 3.2 tab system.

Editing files without a second workflow

The thing nobody mentions: you will want to look at a file the agent changed, in your editor, not in vim over a laggy link.

# mount the remote directory locally
brew install --cask macfuse
brew install gromgit/fuse/sshfs-mac
mkdir -p ~/mnt/devbox
sshfs devbox:/home/peter/code ~/mnt/devbox -o follow_symlinks,reconnect

Workable, occasionally flaky. The lower-friction answer for most people is your editor's own remote support — VS Code Remote-SSH, or JetBrains Gateway — which runs a server on the far side and gives you real editing. Either way, do not end up in a workflow where reading a diff means scp.

What I would skip

Do not run the agent without a multiplexer, ever. Not "for a quick one." The quick ones are when the lid closes.

Do not forward X11 to get notifications. It is slow, it is a security surface, and the bell already works.

Do not put your API keys in the remote shell profile on a shared box. Use a per-session export, or a secrets manager. A ~/.bashrc on a machine other people can read is a credential leak with extra steps.

Do not assume the remote box has your git identity. It does not, and you will find out when the agent's first commit is authored by peter@ip-10-0-4-22.

The short version

Persistent SSH config with keepalives and multiplexing. tmux or Zellij on the far side, always. COLORTERM=truecolor and the tmux override, verified not assumed. A bell-based notification, because bells survive the wire. And your editor doing the file reading, not you doing it in a terminal over 60ms of latency.

For the local half of the setup, the complete Claude Code terminal setup covers the rest.

MOLTamp is a skinnable terminal shell built for agent work — session monitoring, ⌘P search across up to 50 sessions, and a marketplace of skins other people made. Download it, or see the skinning docs.