You are deep in a Composer session on a remote dev box over SSH Remote. Halfway through a refactor, the bottom-right status bar flips red: “Disconnected from <host>”. The agent transcript vanishes, an unsaved buffer turns gray, and reconnecting spawns a new remote extension host that does not see your prior chat. This is almost never Cursor “crashing” — it is the SSH transport dying. The fix lives in three layers: client keepalives, network path stability, and the remote extension host’s own lifecycle. Address them in that order and the drops stop.
Common causes
Ordered by likelihood for SSH Remote disconnects.
1. SSH keepalive interval too long (or zero)
A NAT box, corporate proxy, or cloud load balancer silently drops idle TCP flows after 60-300 seconds. If your ServerAliveInterval is unset or higher than the NAT timeout, the connection looks dead to the middlebox and gets reaped.
How to spot it: Drops happen after a quiet pause (you stepped away, agent was thinking). Editing constantly never drops. ~/.ssh/config has no ServerAliveInterval.
2. Wi-Fi roam or VPN handover
Your laptop hops Wi-Fi access points, your VPN reconnects, or your IP changes when a corporate gateway re-leases. The original TCP socket is dead; Cursor’s resilience layer cannot re-bind.
How to spot it: Drop correlates with walking between rooms, sleep/wake, or tailscale status showing a relay swap. The macOS console log shows en0: link state change near the drop.
3. Remote extension host OOMs
The cursor-server process on the remote box hits its cgroup or container memory cap. The host dies, the SSH socket stays up briefly, then Cursor reports lost connection.
How to spot it: dmesg | tail on the remote shows Out of memory: Killed process ... cursor-server. Or the remote ~/.cursor-server/data/logs/<date>/remoteagent.log ends with FATAL.
4. Disk-full on remote $HOME
Cursor writes telemetry, logs, and language-server caches under ~/.cursor-server. A full disk causes the host to error and exit; the agent looks like it disconnected.
How to spot it: df -h ~ on the remote shows 100% use. ~/.cursor-server/data/logs/ is multi-GB.
5. Remote box went to sleep / autoscaler killed the instance
Spot instances, ephemeral preview environments, and laptops left lid-closed all terminate without notice. The SSH session dies cleanly from the kernel side.
How to spot it: uptime on reconnect shows the box just rebooted. Cloud console shows the instance was replaced or preempted.
6. Server-side ClientAliveInterval mismatch
If the remote sshd_config has ClientAliveInterval 30 and ClientAliveCountMax 3, the server kicks the client after 90s of one-way silence — even if the client thought everything was fine.
How to spot it: Drops are very consistent (every ~90s or 120s of idle). Remote /var/log/auth.log shows Timeout, client not responding.
Before you start
- Note whether drops are time-correlated (always at ~N minutes) or random — deterministic points to a timeout, random points to network or OOM.
- Capture the exact text of the status-bar error and the timestamp.
- Confirm whether vanilla
ssh <host>from a terminal also drops under the same idle pattern. If it does, the issue is purely SSH transport, not Cursor. - Record your
~/.ssh/configkeepalive settings before changing anything.
Information to collect
- Output of
ssh -v <host>for the first 10 lines (proves which config + identity loaded). ~/.ssh/configHostblock for the dev box.- Remote
dmesg | tail -50immediately after a drop. - Remote
df -h ~anddu -sh ~/.cursor-server. - Tail of
~/.cursor-server/data/logs/<latest>/remoteagent.logon the server. - Whether you are on Wi-Fi vs ethernet, and whether a VPN/Zero-Trust agent is in the path.
Step-by-step fix
Ordered cheapest to most invasive.
Step 1: Enable client keepalives in ~/.ssh/config
Add or update the Host block:
Host devbox
HostName 10.0.0.42
User you
ServerAliveInterval 30
ServerAliveCountMax 6
TCPKeepAlive yes
ServerAliveInterval 30 sends a keepalive every 30 seconds — well under typical NAT idle timeouts (60-300s). ServerAliveCountMax 6 means six missed replies (3 minutes) before declaring dead, which tolerates brief Wi-Fi blips. Restart Cursor’s remote window after editing — the SSH config is read fresh on each new connection, not live.
Step 2: Move logs/cache off the home volume on the remote
If df -h ~ shows pressure:
mkdir -p /var/tmp/cursor-server
ln -snf /var/tmp/cursor-server ~/.cursor-server-logs
Then in Cursor settings (Remote: Server Data Path) point at the larger volume. Also clean old logs:
find ~/.cursor-server/data/logs -mtime +7 -delete
This eliminates cause #4 entirely and usually shrinks the install by several GB.
Step 3: Raise the remote extension host memory budget
If dmesg shows OOM kills, the cursor-server Node process needs more heap. Edit your remote shell rc:
echo 'export NODE_OPTIONS="--max-old-space-size=4096"' >> ~/.bashrc
Then kill the running server so it picks up the new env:
pkill -f cursor-server
Cursor will spawn a fresh remote host on the next connection. For repos over 100k files, 8192 is more realistic.
Step 4: Add a server-side keepalive that matches the client
On the remote, edit /etc/ssh/sshd_config (or the user-level ~/.ssh/sshd_config.d/ if your sshd is configured for it):
ClientAliveInterval 30
ClientAliveCountMax 6
Reload sshd:
sudo systemctl reload sshd
Symmetric intervals prevent the case where the server impatiently kicks a client that thought it was healthy.
Step 5: Switch to a stable transport if the network is hostile
For flaky Wi-Fi or VPN handovers, mosh or tailscale ssh survives IP changes:
brew install mosh
Cursor itself does not speak mosh, but for the underlying box you can wrap a stable tunnel:
tailscale up
ssh -o ProxyCommand="tailscale nc %h %p" devbox
Or use Cursor’s Tunnels feature instead of raw SSH — it auto-reconnects on IP changes.
Step 6: Treat the remote as ephemeral and persist state explicitly
If your dev box is a spot/preemptible instance, save the things that matter outside the box:
# Push uncommitted work to a wip branch every 5 min via a cron on the remote
*/5 * * * * cd ~/proj && git add -A && git commit -m "wip" --allow-empty && git push origin HEAD:wip-$(hostname)
Combine with Cursor’s Chats: Persist Remote History setting so the agent transcript syncs to your local profile. Now a drop costs you nothing.
Verify
- Open Cursor on the remote, leave it idle for 10 minutes, return — status bar stays green.
- Run a deliberately long agent task (large refactor) and watch turn-by-turn — no mid-stream disconnect.
- After a real network blip (toggle Wi-Fi for 5 seconds), the bottom bar should reconnect within 30s without losing the open editors.
Long-term prevention
- Ship a team-wide
~/.ssh/configtemplate withServerAliveInterval 30baked in. - Provision dev boxes with at least 8 GB RAM and a logs/cache volume separate from
$HOME. - Add a daily cron to prune
~/.cursor-server/data/logsolder than 7 days. - Prefer Tailscale or Cursor Tunnels over raw SSH for laptop-to-cloud sessions.
- For spot instances, automate
wipbranch pushes every few minutes. - Monitor
dmesgand disk usage in your dev-box bootstrap script; alert before the box dies.
Common pitfalls
- Setting
ServerAliveInterval 300because “less chatter is better” — most corporate NATs drop idle flows in 120s. - Editing
~/.ssh/configwhile Cursor still has an open remote window. The change does not apply until the next connection. - Assuming the agent transcript is saved server-side. By default it lives in the local profile; a remote crash plus a stale local cache loses it.
- Running
pkill cursor-serverwhile editing — the buffer goes gray and any unsaved change vanishes. Save first. - Blaming Cursor when
ssh devboxfrom a plain terminal also drops at the same interval. - Ignoring the remote
~/.cursor-server/data/logs/directory growing into the tens of GB.
FAQ
Q: Cursor reconnects but the agent forgot everything. How do I keep the chat?
Enable Chats: Persist Remote History in settings — the transcript syncs to your local profile and survives remote host restarts. See also Cursor chat history lost on restart.
Q: My SSH config changes do not take effect.
Cursor reads ~/.ssh/config only when a new remote window is opened. Close the remote window, then Cmd+Shift+P -> “Reload Window” is not enough. Use “Remote: Close Remote Connection” first.
Q: Should I use a persistent screen/tmux on the remote?
It does not help Cursor — the remote extension host is a separate process from your shell. But a tmux for terminals inside the remote is still valuable as a fallback when the extension host restarts.
Q: Is mosh supported directly?
No, Cursor’s Remote-SSH transport uses standard SSH. Use mosh for a separate terminal session and Tailscale or Cursor Tunnels for the IDE itself.