Get started
VS Code Remote SSH and Jupyter on a GPU server
Edit, run and debug on the server's GPUs from your laptop, keep notebooks private behind SSH and move datasets efficiently.
On this page
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 named gpu1.
Connect VS Code with Remote-SSH
- In VS Code, install the Remote - SSH extension published by Microsoft.
- Open the Command Palette (F1), run Remote-SSH: Connect to Host... and choose
gpu1. Hosts from your~/.ssh/configappear in the list. - On the first connection, VS Code installs its server component in
~/.vscode-serveron the GPU server. This takes a minute. - Open a folder on the server with File > Open Folder, for example
/data/projects. - 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
- 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 - Start it inside tmux, listening on the loopback interface only:
It prints a URL that contains a token, such astmux new -s lab source ~/venvs/lab/bin/activate jupyter lab --no-browser --ip=127.0.0.1 --port=8888http://127.0.0.1:8888/lab?token=.... Detach with Ctrl+B, then D. - On your laptop, open the tunnel and leave it running:
ssh -N -L 8888:127.0.0.1:8888 gpu1 - 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 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.
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, use the private address so the transfer stays off the public interface. Paths under /data assume you have mounted your NVMe data drives 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 liston the server to print the current URL and token.
Still stuck?
Send us the details — server IP, what you ran and any error output. An engineer replies 24/7.