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 buildand 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 pulland 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
pingandmtragainst 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_keysCheck 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/fstabdd 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 --systemKernel 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 3ServerAliveInterval 15andServerAliveCountMax 3probe 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 sleepssshcan hang on a dead connection for a long time.IdentitiesOnly yesoffers only this key. Otherwise, with five or six keys in the agent, the server cuts you off withToo many authentication failuresbefore it gets to the right one:MaxAuthTriesdefaults 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 10mWindows 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
- Install Microsoft's Remote - SSH extension (
ms-vscode-remote.remote-ssh). - Press
F1→ Remote-SSH: Connect to Host... →devbox, platform Linux. - 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:5432A 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-$COMMITInstall extensions with Extensions: Install from VSIX....
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 workTo detach and leave the jobs running: Ctrl+B, then D.
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-serverand is downloaded fromdownloads.cursor.com;remote.SSH.localServerDownloadworks 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~/.profileor~/.bashrc, tmux autostart or login prompts break it. So donoexecon/run/user/<uid>and a stuck old server — kill it withpkill -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
-Xmxin the backend's.vmoptionsfile 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
.bashrcbreaks 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:
- Install Docker on the server — see "Docker and Docker Compose".
- Connect with Remote-SSH and open the project folder.
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
dockergroup is root. Anyone in it candocker run -v /:/hostand change any file on the server. The VS Code docs themselves suggestusermod -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.appPortand-pinrunArgspublish 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@$USERThe 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: falseAfter 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".
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 statsshows actual usage anddocker system dfshows 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 pushover SSH from the server. Remote-SSH doesn't turn it on by itself: theremote.SSH.enableAgentForwardingsetting only honorsForwardAgentfrom~/.ssh/config. The VS Code docs showForwardAgent yesunderHost *— 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 forHost devbox, andssh-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 devboxworks, 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 failedorcurl download failed. VS Code Server failed to download on the VPS. From the server, runcurl -sI https://update.code.visualstudio.com: DNS, outbound 443 or a proxy. The workaround islocalServerDownloadfrom Step 4; for Cursor, the same applies todownloads.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 withssh -O exit devbox, and keep long jobs in tmux.
Sources
- VS Code: Remote Development using SSH, FAQ, Linux prerequisites, Tips and Tricks, inotify limit, Develop on a remote Docker host, Sharing Git credentials
- VS Code source: check-requirements-linux.sh, download_cache.rs, ipc.net.ts, serverLifetimeService.ts; issues #324809 and #246885
- Cursor: in-house Remote extensions, agent on the remote host, build for older glibc, install failures caused by the shell
- JetBrains: system requirements, prerequisites, Toolbox App requirements, troubleshooting, security model, Gateway to Toolbox migration, Split Mode, inotify limit, licensing, Toolbox App 2.6, Fleet, Code With Me, backend memory report
- code-server: requirements, guide, FAQ, CVE-2025-47269; openvscode-server releases
- Docker and Dev Containers: Packet filtering and firewalls, Port publishing, Docker daemon attack surface, Rootless mode, devcontainer.json reference
- OpenSSH and Linux: ssh_config(5), sshd_config(5), ssh-add(1), agent restriction, kernel inotify, swapon(8)