devmaker.net
start/ linux/ claude-code-ssh-sessions-tmux-mosh
Linux

Claude Code over SSH: Keep Sessions Alive with tmux & mosh

If you drive Claude Code on a remote server over SSH, you know the pain: the connection drops – sometimes you can revive the session, sometimes it's dead. The trick is to decouple the process's lifetime from the connection. I show my setup of mosh and tmux that turns a drop into a mere detach instead of a kill – and how to pick up right where you left off even after a real crash.

Harry_im_Homelab31 (Portrait)
Harald
2026-06-28 · ~10 min read
Claude Code over SSH: Keep Sessions Alive with tmux & mosh

Two Kinds of Drop – Only One Is a Real Problem

If sessions keep breaking off while you run Claude Code on a remote server, one distinction is worth making – because the solution follows from it. There are actually two completely different kinds of drop that are easily lumped together:

  • The control connection drops, the process is still alive. That's the case you can “revive” when you reconnect to the server.
  • The claude process itself dies – e.g. via SIGHUP on an SSH drop, an OOM kill or a crash. That's the “beyond saving” case.

The core of the problem: if claude runs as a child process of your SSH session, a disconnect takes the process down with it. So the solution is always the same – decouple the process's lifetime from the connection.

Primary Fix: tmux on the Server

Never run Claude Code directly in the bare SSH session, always run it inside tmux (or screen). Then a disconnect is only a detach, not a kill:

bash
# on the server, idempotent (attaches if present, otherwise creates):
tmux new -A -s cc
claude

# detach:             Ctrl-b  d
# after a drop, just hop back in:
tmux attach -t cc      # short: tmux a -t cc

This makes practically the entire “beyond saving” case disappear, because the vast majority of drops are pure connection drops – the process keeps running in tmux, unfazed.

This assumes, of course, that the server stays up. In my case that's a power-efficient mini-PC in the homelab – enough RAM for a few Docker stacks and, well, a persistent tmux session that claude hangs on:

What tmux Actually Does

tmux is a terminal multiplexer. The key is its client-server architecture: on the first invocation a tmux server starts in the background as a daemon. Your terminal is only a client. Every program you start inside tmux runs as a child process of that server – not as a child of your SSH session. If SSH drops, only the client disappears; the server, including claude, keeps running, and on reconnect you re-attach to the still-living server.

The hierarchy is server → sessions → windows → panes: a session is a self-contained workspace (e.g. one per project), a window is like a tab, a pane a subdivision of the window.

bash
tmux new -A -s cc        # create OR attach session "cc" if it exists
tmux ls                  # list all sessions
tmux attach -t cc        # attach to session "cc"   (short: tmux a -t cc)
tmux kill-session -t cc  # terminate session

The -A in tmux new -A -s cc means attach-or-create: if “cc” already exists, it attaches instead of erroring out. That makes the command idempotent – the same command works on the first start and on every reconnect. That's exactly why you use it.

From the inside, tmux is controlled via the prefix key Ctrl-b: press the prefix, release, then the command key. The most important ones:

  • Ctrl-b d – detach (disconnect, session keeps running)
  • Ctrl-b c – new window
  • Ctrl-b n / p – next / previous window
  • Ctrl-b 0–9 – jump straight to a window number
  • Ctrl-b % / " – split pane vertically / horizontally
  • Ctrl-b + arrow key – move between panes
  • Ctrl-b z – zoom the active pane (toggle)
  • Ctrl-b [ – scroll/copy mode (exit with q)

The scroll mode in particular is handy, because a bare terminal over SSH often has no usable scrollback – tmux gives you scrollable history here.

Making the Transport More Robust: SSH Keepalive

Before we swap out the transport entirely: two knobs in the SSH config reduce the dropping itself. ServerAliveInterval sends a regular sign of life so the connection isn't silently cut by firewalls or NAT timeouts:

bash
# ~/.ssh/config
Host myserver
    ServerAliveInterval 30
    ServerAliveCountMax 6
    TCPKeepAlive yes

mosh: The Transport That Doesn't Drop

mosh (mobile shell) replaces SSH as the interactive transport and solves exactly its weaknesses on unstable connections. The connection is initially established over SSH – authentication and starting the mosh-server run via your normal SSH config and keys. After that mosh switches to its own UDP protocol; from then on SSH is no longer involved.

Three properties make the difference:

  • Roaming: the connection is tied to a session ID, not the IP/port tuple. A network switch (Wi-Fi → cellular), the laptop sleeping or a new IP – the session simply survives it, whereas SSH would drop hard here.
  • Intermittent connection: if the network drops briefly, the mosh-server waits and re-syncs by itself when it comes back, without you doing anything.
  • Predictive echo: mosh synchronizes the screen state instead of a byte stream and shows your keystrokes locally, speculatively. That makes even a high-latency connection feel responsive.
bash
mosh user@myserver -- tmux new -A -s cc

Broken down: mosh user@myserver connects via mosh (SSH handshake in the background), -- ends the mosh options, and tmux new -A -s cc is the command mosh runs on the server. The effect: one command from anywhere – and you land straight in your persistent Claude session.

Requirements: mosh must be installed on the client and the server and needs UDP ports 60000–61000 reachable – so open the firewall or port forwarding in the respective VM/LXC if needed. Over Tailscale it goes through anyway. What mosh can't do: its own scrollback (that's what tmux underneath is for) and port forwarding/X11 – set up a tunnel separately via ssh -L when you need one.

Recovery for Real Crashes: claude --resume

If the process does die hard once (OOM, kernel panic, power loss), nothing is lost anyway: Claude Code continuously logs the session locally. The transcripts live as JSONL under ~/.claude/projects/<project>/<session-id>.jsonl, where <project> is your working directory with special characters replaced. To resume:

bash
claude -c                    # latest session in the current directory (--continue)
claude --resume              # interactive picker
claude --resume <name|id>    # target a specific session

--continue (short -c) automatically finds the most recent session in the current working directory. Important: start from the same directory the session began in – ID resolution is limited to the project directory and its Git worktrees.

Two habits make reviving more reliable: name your sessions (/rename or claude -n "feature-xy") so you can find them unambiguously in the picker instead of guessing between cryptic IDs. And on resume, use the “continue from summary” option when the session is large – it saves tokens on re-reading.

My Complete Setup

My recommended setup stacks three layers: mosh → tmux → claude. That makes the control connection irrelevant, and for the rare real crash you have the on-disk transcript plus named sessions as a safety net. One command, from anywhere:

bash
mosh user@myserver -- tmux new -A -s cc
# then inside tmux:  claude

mosh ensures that in everyday use you practically never notice a drop; tmux is the safety net for the cases where everything does break down. The point at which a session was “beyond saving” should disappear with this.

What I Left Out

  • tmux-resurrect / tmux-continuum persist the session state to disk and even survive a server reboot. For a running claude process that helps little, because the process state isn't serializable – that's where the --resume layer steps in instead.
  • tmux does not survive a server reboot. Disconnect and logout yes, a restart no – then only the on-disk transcript helps.
  • mosh has limits: no scrollback of its own (hence tmux underneath), no port forwarding/X11. Both are bearable in everyday use.

Conclusion

The actual takeaway is simple: as long as claude runs as a child of the SSH session, every disconnect is a gamble. Decouple the process with tmux and make the transport robust with mosh, and “beyond saving” turns into a harmless detach. And for the rest, there's claude --resume.

// More recommendations

Ad · Affiliate link – if you buy through it, I may earn a commission. It doesn’t change the price for you.

// related posts

> echo "your thoughts" >> claude-code-ssh-sessions-tmux-mosh.responses

Post your comment

Required for comment verification