How do I open a web app running on a cloud GPU with SSH port forwarding?

Start the app on the instance bound to 127.0.0.1, then run ssh -N -L <local-port>:127.0.0.1:<app-port> -p <ssh-port> <ssh-user>@<ssh-host> on your own computer and open http://localhost:<local-port> in your browser; the traffic goes through your existing SSH login and the app’s port is never opened to the internet.

Updated 2026-10-27

This guide follows the official OpenSSH manuals (ssh, ssh_config, sshd_config), the ss manual, the TensorBoard README, the Gradio and Streamlit docs and Microsoft’s OpenSSH for Windows page, all read on 2026-10-27, and CloudGPU’s own /deploy, /docs and /terms pages, read the same day.

When do you need port forwarding?

Many GPU tools show their interface or API on a local web port: TensorBoard, Gradio demos, Streamlit apps, Gradio-based image UIs, or an OpenAI-compatible server such as vLLM. On a rented GPU those ports are on the instance, not on your computer. Our vLLM page notes that the public HTTPS URL is provided for the API templates (Ollama and ComfyUI); for your own app on a Raw GPU, forward the port over SSH.

App Default port (official docs) Default address Source
TensorBoard 6006 localhost since TensorBoard 2.0 TensorBoard README
Gradio 7860 127.0.0.1 Gradio Environment Variables guide
Streamlit 8501 not set (server.address is unset by default) Streamlit config.toml reference
vLLM (vllm serve) 8000 see our vLLM page our vLLM page (-L 8000:localhost:8000)

What you need

  • On CloudGPU: an instance from /deploy using the Raw GPU option, which our deploy page describes as “a raw GPU with JupyterLab + CUDA pre-installed. SSH access, root on a dedicated container”. Each running instance shows an SSH host, port, user and password in your dashboard.
  • On your computer: an SSH client. macOS and Linux include ssh. On Windows, Microsoft’s Get started with OpenSSH for Windows page explains how to add the OpenSSH Client optional feature on Windows 10 (build 1809) and later; the ssh -L commands below work the same in PowerShell.

Port forwarding works the same on every card. Cards listed on /pricing on 2026-10-27 include the RTX 5090 32G, RTX 4090 48G, RTX 4090 24G, RTX 3090 24G and A100 80G. VRAM need depends on model, precision and workflow; see How much VRAM do I need to run an LLM?.

GPU time is billed per minute, rounded up, with a 1-minute minimum, and billing stops when you destroy the instance (see /terms). A forwarded port does not change billing: the instance is billed while it runs, whether or not a tunnel is open.

Step 1: Deploy a Raw GPU and copy the SSH details

  1. Open /deploy and choose Raw GPU (under “Need SSH + your own environment?”).
  2. Pick a card and deploy.
  3. When the instance is running, copy its SSH host, port, user and password from the dashboard. The commands below use the placeholders <ssh-host>, <ssh-port> and <ssh-user>.

Step 2: Log in and check the environment

ssh -p <ssh-port> <ssh-user>@<ssh-host>

On the instance:

nvidia-smi          # GPU name, driver version, VRAM
nvcc --version      # CUDA toolkit version
python3 --version
ss -ltn             # TCP ports that something is already listening on

If nvcc is not found, the CUDA toolkit is not on PATH in that shell; check for a CUDA folder with ls -d /usr/local/cuda* and add its bin directory to PATH. If ss is not found, install it with apt-get update && apt-get install -y iproute2 on a Debian or Ubuntu based image. Note which ports are already in use (for example by JupyterLab) so you start your app on a free one.

Step 3: Start your app on 127.0.0.1

Keep the app on the instance’s loopback address. The tunnel reaches it there, and nothing else on the internet can.

TensorBoard. According to the official TensorBoard README, “In TensorBoard 2.0 and up, --host localhost is the default”, and --bind_all restores “the old behavior of serving to the public network”. So leave --bind_all off:

tensorboard --logdir /workspace/runs --port 6006

Gradio. According to the official Gradio Environment Variables guide, GRADIO_SERVER_PORT defaults to 7860 and GRADIO_SERVER_NAME defaults to “127.0.0.1”; setting it to “0.0.0.0” makes Gradio “accessible from any IP address”. Keep the default:

export GRADIO_SERVER_NAME=127.0.0.1
export GRADIO_SERVER_PORT=7860
python3 app.py

Streamlit. The official Streamlit config.toml reference lists server.port with a default of 8501 and server.address as unset by default, and the configuration options page shows that options can be passed as flags to streamlit run. Set the address explicitly:

streamlit run app.py --server.address 127.0.0.1 --server.port 8501

vLLM. See Rent a cloud GPU for vLLM for the install and vllm serve steps; it serves on port 8000.

Long-running apps should run inside tmux (or screen) so they keep running if your SSH session drops.

Step 4: Open the tunnel from your computer

Run this on your computer, in a new terminal, not on the instance:

ssh -N -L 6006:127.0.0.1:6006 -p <ssh-port> <ssh-user>@<ssh-host>

Enter the instance password when asked. The terminal then prints nothing and stays open; that is the tunnel running. Open http://localhost:6006 in your browser. Press Ctrl+C to close the tunnel.

What the parts mean, according to the official OpenSSH ssh manual:

  • -L “Specifies that connections to the given TCP port or Unix socket on the local (client) host are to be forwarded to the given host and port, or Unix socket, on the remote side.” The 127.0.0.1 in the middle is resolved on the instance, so it means the instance’s own loopback address, not your computer.
  • -N means “Do not execute a remote command. This is useful for just forwarding ports.”
  • -p is the port to connect to on the remote host, which is the instance’s SSH port from your dashboard.

The local port does not have to match. If 6006 is already taken on your computer, use another one, for example -L 16006:127.0.0.1:6006, and open http://localhost:16006. The same manual notes that only the superuser can forward privileged ports, so pick a local port above 1024.

Step 5: Forward several ports at once

Repeat -L for each app:

ssh -N \
  -L 6006:127.0.0.1:6006 \
  -L 7860:127.0.0.1:7860 \
  -L 8501:127.0.0.1:8501 \
  -L 8000:127.0.0.1:8000 \
  -p <ssh-port> <ssh-user>@<ssh-host>

If you open the same tunnels every session, put them in ~/.ssh/config on your computer. According to the official ssh_config manual, LocalForward “Specifies that a TCP port on the local machine be forwarded over the secure channel to the specified host and port from the remote machine”, and “Multiple forwardings may be specified”:

Host cloudgpu
    HostName <ssh-host>
    Port <ssh-port>
    User <ssh-user>
    LocalForward 6006 127.0.0.1:6006
    LocalForward 7860 127.0.0.1:7860
    ServerAliveInterval 30
    ExitOnForwardFailure yes

Then ssh -N cloudgpu opens all of them. The same manual explains the last two lines: ServerAliveInterval makes ssh send a message to the server after that many seconds without data (the default is 0, meaning never), and ExitOnForwardFailure makes ssh terminate the connection if it cannot set up all requested port forwardings, instead of running with a missing tunnel. A new instance gets new SSH details, so update HostName, Port and User each time.

Step 6: Check what is listening on the instance

On the instance:

ss -ltnp

According to the ss manual, -l shows only listening sockets, -t TCP sockets, -n numeric ports and -p the process using each socket. In the local address column, 127.0.0.1:<port> can only be reached from the instance itself (and through your tunnel). An app shown on 0.0.0.0:<port>, *:<port> or [::]:<port> listens on every interface; restart it with the 127.0.0.1 settings from Step 3 unless you meant to expose it.

  • Gradio share=True: according to the official Gradio sharing guide, it creates a public link that is served through the Gradio Share Servers, “Share links expire after 1 week”, and “share links are publicly accessible, meaning that anyone can use your model”. If you use one, add a login with the auth= argument to launch(), which the same guide describes. For a private session, the SSH tunnel is simpler.
  • The instance’s own service URL: our /terms say each running instance is given a public service URL, that these URLs “are not authenticated by default”, and that you should treat them as a credential. Our vLLM page says the public HTTPS URL is provided for the API templates; which port, if any, it reaches on a Raw GPU is not documented on our pages 「待确认」, so use the tunnel for apps you start yourself.

Check your own setup

These are checks you can run yourself; this page does not assume the result.

  • Does the instance allow TCP forwarding? Open the tunnel and load the page. If the page does not load and ssh prints an “open failed” message that says the request was prohibited, the SSH server may have forwarding turned off. According to the official sshd_config manual, AllowTcpForwarding defaults to yes, but an image can change it; as root you can check with grep -i AllowTcpForwarding /etc/ssh/sshd_config.
  • Is the app actually running? On the instance, curl -sI http://127.0.0.1:<app-port> should return an HTTP status line before you open the tunnel.
  • Which ports are free? Run ss -ltn on the instance before you pick a port, since JupyterLab or another service may already use one.

Troubleshooting

  • ssh reports “Address already in use” for the local port: another program uses that local port. Pick another local port (-L 16006:127.0.0.1:6006). Add -o ExitOnForwardFailure=yes so ssh stops instead of running without the tunnel.
  • ssh prints an “open failed” / “Connection refused” message when you load the page: the tunnel works but nothing listens on that port on the instance. Check that the app is running and which port it chose with ss -ltnp; Gradio, for example, tries a range of ports when it starts (the Gradio docs describe GRADIO_NUM_PORTS as the number of ports to try), so it may not be on 7860.
  • The browser shows nothing and the tunnel terminal is closed: the SSH connection dropped. Re-run the ssh command; your app keeps running if you started it in tmux.
  • Connection fails and mentions port 22: you left out -p <ssh-port>, so ssh tried its default port.
  • You want to add a forward without reconnecting: in an interactive ssh session, the ssh manual’s ~C escape opens a command line where you can add -L forwards.

Before you destroy the instance

Close your tunnels, copy off anything you want to keep (TensorBoard logs, Gradio outputs), then destroy the instance. Our /terms say that when an instance is destroyed, all data on local instance storage is permanently deleted, and our /docs say stopped instances release their GPU. scp or rsync over the same SSH details works for copying files to your computer. Billing stops when you destroy the instance (see /terms).

FAQ

Do I need to open a firewall port to see Gradio or TensorBoard on a cloud GPU?

No. An SSH local forward (ssh -L) carries the traffic through the SSH connection you already use to log in, so the app can stay bound to 127.0.0.1 on the instance and you open it at http://localhost: on your computer.

What does ssh -N -L 7860:127.0.0.1:7860 do?

According to the official OpenSSH ssh manual, -L forwards connections to a port on your computer to a host and port on the remote side, and -N runs no remote command, which is useful for just forwarding ports. This one forwards port 7860 on your computer to port 7860 on the instance’s own loopback address.

Can I forward several ports in one SSH command?

Yes. Repeat -L once per port, for example -L 6006:127.0.0.1:6006 -L 7860:127.0.0.1:7860, or add several LocalForward lines for the host in ~/.ssh/config.

Why does my browser show nothing even though the tunnel is open?

Usually nothing is listening on that port on the instance, or the app picked a different port. Run ss -ltnp on the instance to see which ports are listening, and check that the app is still running.

Should I start TensorBoard with --bind_all or Gradio with 0.0.0.0?

Not for a private session. The TensorBoard README says --bind_all serves to the public network, and the Gradio docs say 0.0.0.0 makes the app accessible from any IP address. With an SSH tunnel, 127.0.0.1 is enough.

Does an open SSH tunnel cost extra?

No. GPU time is billed per minute, rounded up, with a 1-minute minimum, while the instance runs, whether or not a tunnel is open, and billing stops when you destroy the instance (see /terms).

Sources (read 2026-10-27)