# VS Code Remote SSH and Jupyter on a GPU server

> Use VS Code Remote SSH on your GPU server, run Jupyter on a remote GPU through an SSH tunnel and move data with rsync and scp. Safe defaults, step by step.

Source: https://overgpu.com/docs/remote-development  
Updated: 2026-10-22  
Publisher: OverGPU (https://overgpu.com/)

VS Code Remote SSH lets you work on your GPU server as if it were local: the editor runs on your laptop, while the terminal, the Python interpreter and the GPUs are on the server. This page sets it up, then shows how to run Jupyter on a remote GPU through an SSH tunnel, keep sessions alive with tmux and move data with rsync and scp. It assumes an [SSH config entry](https://overgpu.com/docs/ssh-access) named `gpu1`.

## Connect VS Code with Remote-SSH

1. In VS Code, install the **Remote - SSH** extension published by Microsoft.
2. Open the Command Palette (F1), run **Remote-SSH: Connect to Host...** and choose `gpu1`. Hosts from your `~/.ssh/config` appear in the list.
3. On the first connection, VS Code installs its server component in `~/.vscode-server` on the GPU server. This takes a minute.
4. Open a folder on the server with **File > Open Folder**, for example `/data/projects`.
5. Install the Python and Jupyter extensions on the remote side: in the Extensions view, they show an **Install in SSH: gpu1** button.

To check, open a terminal in VS Code and run `nvidia-smi`. You should see the server's GPUs, and the lower-left corner shows `SSH: gpu1`. Pick your project's virtual environment with **Python: Select Interpreter**. VS Code can also open `.ipynb` notebooks directly over this connection, with no tunnel.

> Processes started in the VS Code terminal depend on the VS Code connection. Start training runs inside tmux, as shown below, so a laptop going to sleep does not end them.

## Run JupyterLab through an SSH tunnel

1. On the server, create an environment and install JupyterLab: `sudo apt install -y python3-venv python3 -m venv ~/venvs/lab source ~/venvs/lab/bin/activate pip install jupyterlab`
2. Start it inside tmux, listening on the loopback interface only: `tmux new -s lab source ~/venvs/lab/bin/activate jupyter lab --no-browser --ip=127.0.0.1 --port=8888` It prints a URL that contains a token, such as `http://127.0.0.1:8888/lab?token=...`. Detach with Ctrl+B, then D.
3. On your laptop, open the tunnel and leave it running: `ssh -N -L 8888:127.0.0.1:8888 gpu1`
4. Paste the full URL, token included, into your browser.

To check, open a Terminal from the JupyterLab launcher and run `nvidia-smi`: you should see the server's GPUs. Install your framework in the same environment so notebooks can import it; the [PyTorch and vLLM guide](https://overgpu.com/docs/pytorch-vllm) covers the install.

> Never start Jupyter with `--ip=0.0.0.0` on a public IP, and never disable its token or password. Anyone who reaches a Jupyter server can run commands with your user's rights. Keep it on 127.0.0.1 behind the tunnel. In Docker, publish it as `-p 127.0.0.1:8888:8888`, because published ports bypass ufw, as explained in the [firewall guide](https://overgpu.com/docs/networking-firewall).

## Keep sessions alive with tmux

tmux keeps shells and their processes running on the server after you disconnect. Install it with `sudo apt install -y tmux` if it is missing. The commands you will use most:

| Action | Command or keys |
| --- | --- |
| New named session | `tmux new -s train` |
| Detach | Ctrl+B, then D |
| List sessions | `tmux ls` |
| Reattach | `tmux attach -t train` |
| New window, next window | Ctrl+B, then C; Ctrl+B, then N |
| Scroll back | Ctrl+B, then [ (Q to exit) |
| End a session | `tmux kill-session -t train` |

Sessions survive disconnects, not reboots. Send training output to a file as well, for example `python3 train.py 2>&1 | tee train.log`, so you can read it later.

## Move data with rsync and scp

rsync copies only what changed and resumes interrupted transfers, which matters for datasets and checkpoints. It must be installed on both ends: `sudo apt install -y rsync` on Ubuntu, and on Windows use WSL or scp.

```
# upload a dataset (the trailing slash copies the folder's contents)
rsync -avP ./dataset/ gpu1:/data/dataset/

# download checkpoints
rsync -avP gpu1:/data/checkpoints/run-01/ ./run-01/

# preview a mirror before running it for real
rsync -avP --delete --dry-run ./code/ gpu1:/data/projects/code/
```

`-a` keeps permissions and timestamps, `-v` lists files, and `-P` shows progress and keeps partial files so a rerun resumes. `--delete` removes destination files that are missing from the source, so try it with `--dry-run` first. For a single file, scp is enough:

```
scp model.safetensors gpu1:/data/models/
scp gpu1:/data/results/metrics.csv .
```

Between two of your servers, run rsync on one and target the other. With [private networking](https://overgpu.com/products/private-networking), use the private address so the transfer stays off the public interface. Paths under `/data` assume you have [mounted your NVMe data drives](https://overgpu.com/docs/storage-nvme) there.

## Troubleshooting

- **VS Code cannot connect, but ssh gpu1 works.** Run **Remote-SSH: Kill VS Code Server on Host...** and connect again. A full home directory also breaks the install; check it with `df -h ~`.
- **bind: Address already in use when opening the tunnel.** Another program on your laptop uses port 8888. Pick another local port, for example `-L 8890:127.0.0.1:8888`, and open port 8890 in the browser.
- **Jupyter rejects the token.** With the environment activated, run `jupyter server list` on the server to print the current URL and token.

## Related pages

- [SSH into a GPU server](https://overgpu.com/docs/ssh-access): Create an ed25519 key on macOS, Linux or Windows, connect, add teammates and disable password login. Copy-ready steps.
- [Serve an LLM with vLLM](https://overgpu.com/docs/pytorch-vllm): Set up Python with uv, install PyTorch and vLLM, test the OpenAI-compatible API and run it as a service.
- [Mount NVMe drives on Ubuntu](https://overgpu.com/docs/storage-nvme): Find them with lsblk, format and add to fstab by UUID, or build an NVMe RAID 0 with mdadm. Plus health checks and backups.
- [GPU server bandwidth and egress fees](https://overgpu.com/help/bandwidth-and-egress): 20 TB or 50 TB of outbound traffic per month, inbound not counted, no egress fees. See what counts.
