How Tabby's SSH Jump Host Management Works: A Deep Dive into the Internals

Tabby implements SSH jump host support by recursively creating nested SSH sessions that forward TCP channels through intermediate hosts, using a hierarchical multiplexer key system to cache and reuse connection chains.

Tabby is an open-source terminal emulator built on Electron that provides advanced SSH connection capabilities. Understanding how Tabby SSH jump host management works internally reveals a layered architecture that enables multi-hop connections while optimizing resource usage through intelligent session reuse. The implementation spans across the TypeScript session management layer and leverages the underlying russh Rust library for transport abstraction.

The Three-Component Architecture

Tabby orchestrates jump host connections through three tightly integrated components. The SSHTabComponent handles recursive session instantiation, the SSHSession class manages transport selection and authentication, and the SSHMultiplexerService generates deterministic keys for connection caching.

When configuring a jump host, users set the jumpHost property in an SSH profile to reference another profile's ID:

{
  "type": "ssh",
  "options": {
    "host": "target.example.com",
    "port": 22,
    "user": "alice",
    "jumpHost": "my-gateway",
    "reuseSession": true
  }
}

This configuration triggers a recursive setup process rather than a direct TCP connection.

Recursive Session Creation

The setupOneSession() method in tabby-ssh/src/components/sshTab.component.ts builds jump host chains through recursion. When opening a new tab, the component:

  1. Queries sshMultiplexer.getSession(profile) to check for reusable sessions
  2. Resolves the jumpHost profile ID to a full connection configuration
  3. Recursively calls setupOneSession() to establish the jump session
  4. Increments reference counters via jumpSession.ref() to prevent premature closure
  5. Opens a TCP forward channel through the authenticated jump session
session.jumpChannel = await jumpSession.ssh.openTCPForwardChannel({
    addressToConnectTo: profile.options.host,
    portToConnectTo: profile.options.port ?? 22,
    originatorAddress: '127.0.0.1',
    originatorPort: 0,
})

This design supports arbitrary chain depth, allowing configurations like gateway-agateway-bfinal-target.

Transport Initialization in SSHSession

The start() method in tabby-ssh/src/session/ssh.ts implements a priority-based transport selection logic. When a jump channel exists from the previous step, Tabby initializes a nested SSH transport over that channel rather than a direct socket:

if (this.profile.options.proxyCommand) { … }
else if (this.jumpChannel) {
    transport = await russh.SshTransport.newSshChannel(this.jumpChannel.take())
    this.jumpChannel = null
}
else if (this.profile.options.socksProxyHost) { … }
else {
    transport = await russh.SshTransport.newSocket(`${host}:${port}`)
}

The newSshChannel() function creates a second-level SSH transport that tunnels all traffic through the already-authenticated jump session. This approach allows the russh library to handle encryption and authentication for the target host while using the intermediate host merely as a byte-forwarding layer.

Hierarchical Multiplexer Keys

Connection reuse in jump host scenarios requires keys that uniquely identify entire connection chains. The SSHMultiplexerService in tabby-ssh/src/services/sshMultiplexer.service.ts generates these identifiers recursively:

let key = `${host}:${port}:${user}:${proxyCommand}:${socksProxyHost}:${socksProxyPort}:${httpProxyHost}:${httpProxyPort}`;
if (profile.options.jumpHost) {
    const jumpConnection = (await this.profilesService.getProfiles())
        .find(x => x.id === profile.options.jumpHost);
    const jumpProfile = this.profilesService.getConfigProxyForProfile<SSHProfile>(jumpConnection);
    key += '$' + await this.getMultiplexerKey(jumpProfile);
}

The $ separator concatenates each jump host's key to create a hierarchical identifier. When addSession() stores a connection under this composite key, subsequent calls to getSession() with identical chain configurations retrieve the same underlying SSHSession. This mechanism enables multiple terminal tabs to share authenticated jump host connections without creating redundant network sockets.

Resource Cleanup and Reference Counting

Tabby prevents resource leaks through a reference counting system integrated with RxJS observables. Each SSHSession exposes a willDestroy$ observable that child sessions subscribe to during the setup phase.

When a child session closes, it calls jumpSession.unref() to decrement the parent session's reference count. The jump session automatically destroys itself—closing the TCP forward channel and underlying socket—only when this count reaches zero. This ensures intermediate hosts remain available for the duration of all dependent child sessions while releasing resources promptly when no longer needed.

Summary

  • Tabby SSH jump host management uses recursive construction in SSHTabComponent.setupOneSession() to build multi-hop connection chains.
  • TCP forward channels are established through openTCPForwardChannel() and stored in the child session's jumpChannel property.
  • Transport initialization in SSHSession.start() leverages russh.SshTransport.newSshChannel() to tunnel through existing authenticated sessions.
  • Hierarchical multiplex keys using the $ separator enable the SSHMultiplexerService to track entire connection paths and enable session reuse across tabs.
  • Reference counting via ref() and unref() methods ensures jump sessions persist only while actively referenced by child sessions.

Frequently Asked Questions

How does Tabby handle authentication for multiple jump hosts?

Each hop operates as an independent SSHSession instance resolved from its own profile configuration. Because setupOneSession() treats each jump host as a distinct connection, you can use different SSH keys, passwords, or authentication agents for the gateway and final target hosts. The multiplexer system caches each authenticated session separately while maintaining the parent-child relationship through the composite key structure.

Can different terminal tabs share the same jump host connection?

Yes. The SSHMultiplexerService generates deterministic keys that incorporate the entire jump host chain. When opening multiple tabs to the same target through identical intermediate hosts, Tabby retrieves existing authenticated sessions from the cache rather than establishing new TCP connections, provided the profile has reuseSession enabled.

What happens when a jump host connection drops unexpectedly?

When a jump session emits a destruction event through willDestroy$, all child sessions detect the closure via their subscriptions and trigger immediate cleanup. The reference counting mechanism ensures dependent sessions close gracefully, and Tabby's reconnection logic can attempt to rebuild the entire chain if automatic reconnection is configured in the profile settings.

Does Tabby support ProxyJump equivalents with multiple hops?

Yes. The recursive implementation in SSHTabComponent supports arbitrary chain depth through repeated application of the setup logic. You can chain multiple jumpHost references—where profile A jumps through profile B, which jumps through profile C—and the system will construct the appropriate nested transport layers and generate the correct hierarchical multiplex keys for caching.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →