Dokploy / Dokploy/dokploy

Remote terminal leaks SSH/PTY sessions when WebSocket closes during SSH handshake

Open
#5,217 3 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug
Dominant language
TypeScript
Stars
37.4k
Forks
3k
Avg merge
1d 3h
Merged PRs (30d)
73

Description

Summary

In self-hosted Dokploy, closing, refreshing, or reconnecting a remote terminal while the backend SSH connection is still being established can leave SSH sessions and interactive PTY shells running on the target server.

Repeated terminal operations accumulate sshd-session, bash, and PTY processes.

Environment

  • Dokploy: v0.29.8
  • Deployment: self-hosted Docker
  • Terminal type: remote server/container terminal
  • Target: remote Linux server

Observed behavior

The Dokploy container maintained approximately 200 established SSH connections to the same remote server.

On the remote server we observed:

  • approximately 198 SSH sessions originating from Dokploy
  • approximately 198 interactive PTYs
  • approximately 200 bash processes
  • zero zombie processes
  • sessions remaining alive for more than one hour

The SSH connections were owned by the Dokploy Node.js process using ssh2, not by separate SSH client processes.

Expected behavior

When the browser WebSocket closes, Dokploy should immediately terminate:

  1. the pending or established ssh2 client;
  2. the SSH channel/PTY;
  3. the remote shell;
  4. all related event listeners and timers.

No remote SSH/PTY session should remain after the terminal WebSocket is closed.

Reproduction / trigger

  1. Run self-hosted Dokploy with a remote server configured.
  2. Open a remote server or remote container terminal.
  3. While the terminal is connecting or reconnecting, close the terminal, navigate away, refresh the page, or interrupt the network connection.
  4. Repeat the operation several times.
  5. Inspect the target server's SSH sessions and PTY processes.

The issue is more likely to occur when the remote SSH handshake is delayed or interrupted.

Suspected cause

The frontend cleanup only closes the WebSocket when:

webSocket.readyState === WebSocket.OPEN

This does not clean up a connection that is still connecting.

On the backend, the WebSocket close handler for the remote SSH path is registered inside the SSH ready/exec callback. If the WebSocket closes before SSH emits ready, the close event can be missed. The SSH connection may then complete and create a remote shell without a live browser terminal.

The same lifecycle pattern is present in the v0.30.2 source:

Suggested fix

  • Register WebSocket cleanup immediately after creating the SSH client, before calling connect().
  • Make cleanup idempotent and safe for pending, established, and PTY-created states.
  • Destroy pending SSH clients when the WebSocket closes.
  • Do not call shell() or exec() if the WebSocket is already closing or closed.
  • Add an SSH handshake timeout.
  • Add a regression test for WebSocket closure before SSH ready.
  • Verify that normal terminal exit closes both the browser WebSocket and the remote PTY.

Related work

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the four referenced terminal files: docker-terminal.tsx, terminal.tsx, docker-container-terminal.ts, and terminal.ts. Trace cleanup when the WebSocket closes before SSH emits ready, then add a regression test for that handshake timing and verify normal terminal exit. Done means pending and established SSH, PTY, shell, listeners, and timers are cleaned up without creating a session after the WebSocket closes.

Written by the indexing model from the issue text.

Assessment

Tech stack
docker, node.js, typescript
Domain
backend, devops
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.