Remote development on a VPS: VS Code Remote-SSH, Cursor and JetBrains Gateway

Remote development on a VPS with VS Code Remote-SSH, Cursor and JetBrains Gateway: server prep, RAM and CPU needs, Dev Containers, code-server and troubleshooting.

The editor UI stays on your computer while the project, terminal, language servers and builds run on the VPS. Below: how each tool works, what it really needs in resources and what breaks most often. Checked against the docs and source code and current as of September 2026: VS Code 1.139 with Remote-SSH 0.128, Cursor 3.22, JetBrains 2026.2, code-server 4.139.

What you'll need

  • A VPS running Ubuntu 24.04, which all three tools officially support; Ubuntu 22.04 and Debian 12 work too. Anything with glibc below 2.28 — Ubuntu 18.04, Debian 9, CentOS 7 — is no longer officially supported. For CPU and RAM, see "How much CPU and RAM you need" below.
  • SSH key login — see "VNC console and first connection over SSH" and "How to harden SSH on your VPS".
  • Locally: an OpenSSH client (built into macOS, Linux and Windows 10/11) plus VS Code, Cursor or a paid JetBrains IDE. The free tiers of IntelliJ IDEA and PyCharm don't include remote development.
  • For code-server: a domain pointed at the server, see "Pointing a domain at your server".

Step 1. Weigh what you gain and what you pay

What you gain:

  • Cores and NVMe. npm install, cargo build and IDE indexing are bound by CPU and disk. Your laptop stays cool and can be a modest machine.
  • A production-like environment. Same Linux, same package versions, and Docker runs natively instead of inside Docker Desktop's VM.
  • Fast downloads. git clone, docker pull and dependencies come in over the data center's uplink. On ForestsNet Cloud only outgoing traffic counts toward your plan; incoming is free.

What you pay:

  • Latency. In VS Code and Cursor the editor itself is local; completions, go-to-definition and the terminal travel over the network. In JetBrains the backend handles part of the typing assistance, so JetBrains officially requires at most 200 ms of latency and at least 20 Mbps between client and server. Measure it with ping and mtr against the test addresses of the Looking Glass in the Netherlands and Germany.
  • Network dependency. Short drops are survivable (Step 4); no connection means no work.
  • One more server to run. It needs updates and hardening.
  • Not everything moves. Android and iOS emulators, GPU workloads and GUI apps stay local.

Step 2. Prepare the server

Create a non-root user

The IDE server and its extensions run as whoever connected. Under root, a vulnerable extension or a malicious postinstall script in an npm package owns the whole server. Run as root:

adduser dev
usermod -aG sudo dev
install -d -m 700 -o dev -g dev /home/dev/.ssh
install -m 600 -o dev -g dev /root/.ssh/authorized_keys /home/dev/.ssh/authorized_keys

Check that ssh dev@SERVER-IP works, then close root login — see "How to harden SSH on your VPS".

Add swap

Language servers and builds use memory in spikes. Without swap on a 2–4 GB server, the OOM killer steps in at such a moment and kills the largest process, usually the IDE server. With swap you get a slowdown instead of a crash:

sudo dd if=/dev/zero of=/swapfile bs=1M count=4096 status=progress
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab

dd rather than fallocate: the util-linux docs call it the most portable way to get a file without holes. If swap stays busy all the time, you're short on RAM — see "High load on a VPS: how to find the cause".

Raise the inotify limit

VS Code and JetBrains watch files through inotify, one watch per directory. Since kernel 5.11 the default limit is sized so that watches take at most 1% of RAM, so on a small VPS it's low. A monorepo or a project with .venv hits it: VS Code reports "unable to watch for file changes in this large workspace", and JetBrains falls back to slow directory scans.

cat /proc/sys/fs/inotify/max_user_watches
echo 'fs.inotify.max_user_watches=524288' | sudo tee /etc/sysctl.d/60-inotify.conf
sudo sysctl --system

Kernel memory, about 1 KB per watch, is only spent on watches actually in use. Still exclude large folders via files.watcherExclude; VS Code already skips node_modules.

Don't disable forwarding in sshd

All three IDEs work through SSH tunnels. If a hardening checklist made you set AllowTcpForwarding no or DisableForwarding yes, connecting fails with open failed: administratively prohibited. And man sshd_config says it plainly: as long as users have shell access, this restriction adds no security. Check that you get allowtcpforwarding yes and disableforwarding no:

sudo sshd -T | grep -Ei 'allowtcpforwarding|disableforwarding'

Step 3. Configure SSH on your computer

VS Code, Cursor and Toolbox App call the system OpenSSH client and read ~/.ssh/config (on Windows, %USERPROFILE%\.ssh\config):

Host devbox
    HostName 203.0.113.10
    User dev
    IdentityFile ~/.ssh/id_ed25519
    IdentitiesOnly yes
    ServerAliveInterval 15
    ServerAliveCountMax 3
  • ServerAliveInterval 15 and ServerAliveCountMax 3 probe the server every 15 seconds. After three missed replies, about 45 seconds, the connection is closed and the IDE reconnects. By default there are no probes, and after the laptop sleeps ssh can hang on a dead connection for a long time.
  • IdentitiesOnly yes offers only this key. Otherwise, with five or six keys in the agent, the server cuts you off with Too many authentication failures before it gets to the right one: MaxAuthTries defaults to 6.

Test ssh devbox in a terminal: whatever fails there will fail in the IDE too.

Multiplexing. ControlMaster runs all sessions over one connection, so a password or second factor is asked once. The VS Code docs recommend it when you don't log in with a key:

Host devbox
    ControlMaster auto
    ControlPath ~/.ssh/cm-%C
    ControlPersist 10m

Windows OpenSSH doesn't support it. If the master connection died when the network changed, new sessions wait until ServerAliveInterval closes it; ssh -O exit devbox does it right away. With key login, multiplexing is optional: VS Code already shares one connection across all windows.

Step 4. Connect from VS Code

  1. Install Microsoft's Remote - SSH extension (ms-vscode-remote.remote-ssh).
  2. Press F1 → Remote-SSH: Connect to Host... → devbox, platform Linux.
  3. File → Open Folder → the project folder, for example /home/dev/app.

What gets installed on the server

An install script downloads the code CLI and a server that matches your exact VS Code commit. In 1.139 the server archive is 215 MB and about 620 MB unpacked, a good share of it the bundled Copilot. The CLI lands in ~/.vscode-server/code-<commit> and the server in ~/.vscode-server/cli/servers/Stable-<commit>/server. The server listens on a random port on 127.0.0.1 and only accepts connections through the SSH tunnel with a token, so no ports need opening.

Since March 2026 VS Code ships a stable release every week, and each update installs a new copy on the server. The CLI keeps the five most recent, so the folder grows to 3–4 GB. Clean it up with Remote-SSH: Uninstall VS Code Server from Host....

Extensions

Themes and keymaps run locally, while language servers, linters, debuggers and Git run on the server and have to be installed there, with the Install in SSH: devbox button. Extensions every new host should get go into remote.SSH.defaultExtensions. That also means they eat the server's memory: tsserver alone is capped at 3 GB by default (js/ts.tsserver.maxMemory).

Port forwarding

Start npm run dev on port 5173 and VS Code notices the listening port and forwards it, so you open http://localhost:5173 locally. That's the remote.autoForwardPorts setting, on by default; ports are listed in the Ports tab. For a permanent forward, use ~/.ssh/config:

Host devbox
    LocalForward 127.0.0.1:5432 127.0.0.1:5432

A forwarded port listens only on 127.0.0.1 of your computer. Keep services on the server bound to 127.0.0.1 too and don't open their ports in the firewall.

If the server can't download VS Code Server

By default the VPS downloads VS Code Server itself from update.code.visualstudio.com and vscode.download.prss.microsoft.com; if that fails, VS Code downloads the archive to your computer and copies it over with scp. If outbound HTTPS from the server is blocked, make that the default in your local settings.json:

"remote.SSH.localServerDownload": "always"

Every update then means about 215 MB of upload over your connection.

If your computer is offline too, lay the files out by hand. The commit is the second line of code --version. On a machine with internet access:

COMMIT=04c0d99f4fb0d8afe6ce4f0c58e31e183ac3e4b1
curl -L -o vscode-cli.tar.gz "https://update.code.visualstudio.com/commit:$COMMIT/cli-alpine-x64/stable"
curl -L -o vscode-server.tar.gz "https://update.code.visualstudio.com/commit:$COMMIT/server-linux-x64/stable"
scp vscode-cli.tar.gz vscode-server.tar.gz devbox:

On the server:

COMMIT=04c0d99f4fb0d8afe6ce4f0c58e31e183ac3e4b1
mkdir -p ~/.vscode-server/cli/servers/Stable-$COMMIT/server
tar -xzf vscode-server.tar.gz -C ~/.vscode-server/cli/servers/Stable-$COMMIT/server --strip-components=1
tar -xzf vscode-cli.tar.gz -C ~/.vscode-server
mv ~/.vscode-server/code ~/.vscode-server/code-$COMMIT

Install extensions with Extensions: Install from VSIX....

Important: this directory layout is undocumented — it's what Remote-SSH 0.128 uses, and later versions may change it. Repeat the procedure with the new commit after every VS Code update.

Disconnects and long-running jobs

VS Code reconnects on its own, up to eight attempts, and the server waits three hours for the client, so open files and terminals come back. But a terminal lives only as long as the server does. After you close all windows the server exits in 5 minutes, after a VS Code update you land on a new server, and if the OOM killer takes the server down, everything started in its terminals goes with it. Run long builds and migrations in tmux:

sudo apt install -y tmux
tmux new -s work
tmux attach -t work

To detach and leave the jobs running: Ctrl+B, then D.

Done: the project is open on the server, ports go through the SSH tunnel, and long jobs live in tmux and survive disconnects.

Step 5. Connect from Cursor

Cursor is a VS Code fork, but Microsoft's Remote extensions are licensed for use with Visual Studio Code only. Since May 2025 Cursor has its own Remote SSH, Dev Containers and WSL extensions from Anysphere, and extensions come from Open VSX. Connecting works the same: Remote-SSH: Connect to Host... → devbox. The differences:

  • The server goes to ~/.cursor-server and is downloaded from downloads.cursor.com; remote.SSH.localServerDownload works the same way.
  • Since version 3.10 the server needs glibc 2.28+. Cursor picks a separate build for glibc 2.17–2.27 automatically, but only if the server has internet access.
  • Since September 2026 the agent in Remote SSH windows runs on the server and calls the Cursor API from there. If the server can't reach it, agent traffic is routed through your computer automatically; "cursorAgentHost.remoteInferenceRoute": "always" forces that.
  • The install runs through bash --login, so output in ~/.profile or ~/.bashrc, tmux autostart or login prompts break it. So do noexec on /run/user/<uid> and a stuck old server — kill it with pkill -f cursor-server.

Step 6. Connect from JetBrains: Toolbox App or Gateway

Here the server runs a full IDE without a UI, the backend: it indexes the project, analyzes code and runs builds. You get a thin JetBrains Client, and they talk over an SSH tunnel with TLS 1.3 on top. Remote development is only in paid IDEs: IntelliJ IDEA Ultimate, PyCharm Pro and others. Ways to connect:

  • Toolbox App is the path JetBrains recommends. Since version 2.6 (April 2025) it uses the system OpenSSH, so ~/.ssh/config, ProxyJump and second factors just work.
  • IDE welcome screen → Remote Development → SSH → New Connection.
  • JetBrains Gateway is being deprecated: remote development is moving to Toolbox App, and JetBrains hasn't given an end-of-support date.

The backend installed via Gateway or the IDE goes to ~/.cache/JetBrains/RemoteDev/dist, and the server downloads it from download.jetbrains.com. Without outbound access you can upload it from the client, in which case the server needs free space of at least four times the archive size. Pitfalls:

  • Port and forwarding. The backend needs a free port and AllowTcpForwarding, which rules out shared hosting but not a VPS.
  • The backend is a JVM. When memory runs short, it doesn't crash but hangs. Raise -Xmx in the backend's .vmoptions file if there's physical RAM to back it, and keep swap — JetBrains recommends it even on cloud instances.
  • Plugins are more latency-sensitive. Plugins that aren't split into frontend and backend parts render their UI on the server, and on a slow link it shows immediately.
  • A noisy .bashrc breaks backend deployment as well.

Fleet was discontinued in December 2025, and Code With Me is being wound down after version 2026.1. Neither affects remote development.

Step 7. Run Dev Containers on the server

Dev Containers describe the environment in .devcontainer/devcontainer.json: image, extensions, ports, post-create commands. The simplest way is on top of Remote-SSH, with no Docker needed locally:

  1. Install Docker on the server — see "Docker and Docker Compose".
  2. Connect with Remote-SSH and open the project folder.
  3. F1 → Dev Containers: Reopen in Container.

In Cursor, Anysphere's extension does the same; in JetBrains, Dev Containers work with Docker over SSH. The alternative, DOCKER_HOST=ssh://dev@devbox or docker context on your computer, is harder: you need a local Docker CLI, and bind mounts have to be rewritten to server paths, since Docker can't mount a local folder into a remote container. Keep in mind:

  • The docker group is root. Anyone in it can docker run -v /:/host and change any file on the server. The VS Code docs themselves suggest usermod -aG docker $USER; on a personal server that's acceptable if you know what it means, and rootless Docker is the stricter option.
  • Ports go through forwardPorts, which uses the editor's tunnel. appPort and -p in runArgs publish ports through Docker, which bypasses ufw and exposes them to the internet. If you must publish, bind to localhost: -p 127.0.0.1:5432:5432.
  • Never use DOCKER_HOST=tcp:// without TLS. An exposed Docker API is root for anyone who reaches it.
  • VS Code forwards your SSH agent and Git credentials into the container automatically. Don't open someone else's repository in a container while your agent holds production keys.

Step 8. Open code-server in the browser

code-server by Coder is VS Code in the browser, handy on a tablet or a borrowed computer. Releases follow VS Code: 4.139.1 is built on Code 1.139.1. Install it; the first command only prints the plan:

curl -fsSL https://code-server.dev/install.sh | sh -s -- --dry-run
curl -fsSL https://code-server.dev/install.sh | sh
sudo systemctl enable --now code-server@$USER

The config is ~/.config/code-server/config.yaml. Keep the 127.0.0.1 address and password login; the password can be stored as an argon2 hash (echo -n 'password' | npx argon2-cli -e):

bind-addr: 127.0.0.1:8080
auth: password
hashed-password: "$argon2i$v=19$m=4096,t=3,p=1$..."
cert: false

After editing, run sudo systemctl restart code-server@$USER. Expose code-server only through your own domain with TLS. Caddy is the shortest route, since it gets the Let's Encrypt certificate itself:

code.example.com {
    reverse_proxy 127.0.0.1:8080
}

More in "Caddy: automatic HTTPS" and "Nginx: install and your first site". With Nginx, pass the Host, Upgrade and Connection headers: the editor doesn't work without WebSockets. In the firewall, open only 80 and 443 — see "The ufw firewall: managing ports".

Important: code-server is your server's terminal in a browser. Its login limit, two attempts per minute plus twelve per hour, is global rather than per IP, so a bot can lock you out as well. It's safer to put the domain behind external authentication (oauth2-proxy, Pomerium, Cloudflare Access) or skip publishing and use ssh -N -L 8080:127.0.0.1:8080 devbox. Versions below 4.99.4 are vulnerable: CVE-2025-47269 lets a crafted link steal the session cookie.

code-server installs extensions from Open VSX, where Pylance, C/C++, Live Share and Microsoft's Remote extensions aren't available. Skip Gitpod's openvscode-server for new setups: its last release came out in February 2026 on VS Code 1.109, and its Docker image listens on 0.0.0.0 without a token.

How much CPU and RAM you need

Official minimums cover launching the tool itself; memory goes to language servers and your project. Rough guide:

  • VS Code and Cursor. Microsoft requires 1 GB of RAM and recommends 2 GB and 2 cores. In practice: 2 vCPU and 4 GB for a typical web or Python project, 4 vCPU and 8 GB for a TypeScript or Rust monorepo.
  • JetBrains. The official minimum is 4 vCPU, 8 GB of RAM with 3 GB free for the IDE, and 10 GB of disk, and clock speed matters more than core count. A JetBrains support forum report describes an IntelliJ IDEA 2025.3 backend at about 3 GB idle and up to 8 GB at peaks.
  • Dev Containers add every service in devcontainer.json. docker stats shows actual usage and docker system df shows space taken by images.
  • code-server officially needs 1 GB and 2 cores; in practice it's the same as VS Code.
  • Disk. Up to 3–4 GB for ~/.vscode-server, about 10 GB for the JetBrains backend, plus Docker images. NVMe speeds up anything that touches thousands of small files: indexing, npm install, builds.

Always add 2–4 GB of swap. To see what eats memory, use free -h and ps aux --sort=-rss | head. For picking a server for the rest of your dev work, see the overview "VPS for developers".

Security

  • Agent forwarding isn't needed for the IDE, only for git push over SSH from the server. Remote-SSH doesn't turn it on by itself: the remote.SSH.enableAgentForwarding setting only honors ForwardAgent from ~/.ssh/config. The VS Code docs show ForwardAgent yes under Host * — don't do that. While you're connected, root on the server or a malicious extension can log in to GitHub and other servers with your keys: the keys won't leak, but they'll be used. A separate key on the server with access to a single repository is better — see "Deploying from Git". If you do need forwarding, enable it only for Host devbox, and ssh-add -h (OpenSSH 8.9+) restricts where a forwarded key can be used.
  • Expose only SSH, plus 80 and 443 for code-server. Published Docker ports bypass ufw, so check what the server actually listens on with sudo ss -tlnp. Firewall rules are in "The ufw firewall: managing ports", brute-force protection in "Fail2ban: protection against brute-force".
  • Extensions are code running with your permissions. Install them from known publishers and don't keep production secrets on the dev server.

Troubleshooting

Start with the log: F1 → Remote-SSH: Show Log in VS Code and Cursor, ssh -v devbox in a terminal. If the terminal can't connect either, it's not the IDE — see "Can’t connect via SSH".

  • Could not establish connection to "devbox". The cause is in the log lines above it. If ssh devbox works, run Remote-SSH: Kill VS Code Server on Host... and the server is reinstalled. Check free space (df -h ~) and the server log at ~/.vscode-server/cli/servers/*/log.txt.
  • open failed: administratively prohibited. TCP forwarding is disabled — see Step 2.
  • "The remote host may not meet VS Code Server's prerequisites for glibc and libstdc++". glibc is below 2.28, check with ldd --version. A sysroot workaround is documented, but reinstalling the OS is simpler — see "Reinstalling the OS and choosing a template".
  • wget download failed or curl download failed. VS Code Server failed to download on the VPS. From the server, run curl -sI https://update.code.visualstudio.com: DNS, outbound 443 or a proxy. The workaround is localServerDownload from Step 4; for Cursor, the same applies to downloads.cursor.com.
  • The IDE is slow, the window keeps reconnecting, processes vanish. Probably OOM: sudo dmesg -T | grep -Ei 'out of memory|killed process'. Add swap or RAM and disable heavy extensions; in JetBrains, raise -Xmx. Since summer 2026 the VS Code tracker has open reports of a memory leak with Copilot Chat in Remote-SSH sessions, reaching gigabytes in the remote extension host. If memory drains before your eyes, disable Copilot Chat and compare.
  • "Unable to watch for file changes in this large workspace". The inotify limit is exhausted — see Step 2.
  • Drops when the network changes or after sleep. Add ServerAliveInterval (Step 3), close a stuck master connection with ssh -O exit devbox, and keep long jobs in tmux.

Sources