# How DBX Implements SSH Tunnel Connections for Secure Database Access

> Discover how DBX implements SSH tunnel connections for secure database access. Learn about its layered transport, authentication, and traffic forwarding using the russh crate.

- Repository: [skyler/dbx](https://github.com/t8y2/dbx)
- Tags: how-to-guide
- Published: 2026-07-04

---

**DBX implements SSH tunnel connections as ordered transport layers that automatically chain through SSH, optional proxies, and HTTP tunnels, using the `russh` crate in [`crates/dbx-core/src/db/ssh_tunnel.rs`](https://github.com/t8y2/dbx/blob/main/crates/dbx-core/src/db/ssh_tunnel.rs) to authenticate sessions and forward local TCP traffic to remote databases behind firewalls.**

The open-source DBX project (`t8y2/dbx`) treats SSH tunnels as a **transport-layer abstraction** that sits transparently between your client application and the database. When a connection request includes `transport_layers`, DBX dynamically starts a local listener that forwards traffic through the configured SSH bastion host, eliminating the need for manual port forwarding or VPNs while keeping credentials encrypted in the local SQLite store.

## Transport Layer Architecture Overview

DBX decouples connection security from database drivers by implementing a **chainable transport layer system**. Each layer processes traffic sequentially, with SSH typically forming the innermost secure channel.

### Data Model and Configuration

The transport layer configuration resides in **[`crates/dbx-core/src/models/connection.rs`](https://github.com/t8y2/dbx/blob/main/crates/dbx-core/src/models/connection.rs)**, where the `SshTunnelConfig` struct defines all SSH-specific parameters:

```json
{
  "type": "ssh",
  "id": "bastion-1",
  "host": "bastion.example.com",
  "port": 22,
  "user": "dbadmin",
  "password": "••••",
  "key_path": "~/.ssh/id_rsa",
  "key_passphrase": "••••",
  "connect_timeout_secs": 5,
  "expose_lan": false,
  "use_ssh_agent": true,
  "ssh_agent_sock_path": "/run/user/1000/ssh-agent.sock"
}

```

When loading connections through **[`packages/node-core/src/connections.ts`](https://github.com/t8y2/dbx/blob/main/packages/node-core/src/connections.ts)**, the `normalizeTransportLayers` helper converts legacy configuration fields into this modern `transport_layers` structure. Sensitive credentials like SSH passwords and key passphrases are extracted and stored in the encrypted `connection_secrets` table via **[`crates/dbx-core/src/db/storage.rs`](https://github.com/t8y2/dbx/blob/main/crates/dbx-core/src/db/storage.rs)**, ensuring they never persist in plain text within the configuration object.

### Starting the Tunnel Chain

When a query executes, DBX retrieves the **effective transport layers** by calling `config.effective_transport_layers()`. If this array contains entries, the async function **`start_transport_layers`** in **[`crates/dbx-core/src/db/transport_layer_tunnel.rs`](https://github.com/t8y2/dbx/blob/main/crates/dbx-core/src/db/transport_layer_tunnel.rs)** orchestrates the tunnel creation:

```rust
let local_port = db::transport_layer_tunnel::start_transport_layers(
    &connection.id,
    &transport_layers,
    remote_host,
    remote_port,
    &ssh_tunnel_manager,
    &proxy_tunnel_manager,
    &http_tunnel_manager,
).await?;

```

This function iterates over layers in order, creating a local `TcpListener` for each hop. Each subsequent layer connects through the previous layer's local port, resulting in a single local endpoint that transparently forwards to the ultimate database host.

## SSH Tunnel Implementation Details

The core SSH logic lives in **[`crates/dbx-core/src/db/ssh_tunnel.rs`](https://github.com/t8y2/dbx/blob/main/crates/dbx-core/src/db/ssh_tunnel.rs)**, which handles authentication, session maintenance, and TCP forwarding.

### Authentication Methods

The **`connect_and_authenticate`** function attempts multiple authentication strategies in sequence:

1. **"none" probe** – Discovers available authentication methods supported by the server
2. **Public-key authentication** – Uses the private key specified in `key_path` if provided
3. **Password authentication** – Falls back to password credentials if configured
4. **SSH-agent authentication** – Connects to the agent socket specified in `ssh_agent_sock_path` when `use_ssh_agent` is enabled

The function returns a `russh::client::Handle<SshClient>` representing the authenticated session, which DBX maintains for the connection duration.

### Connection Forwarding and Session Management

After authentication, DBX creates a local `TcpListener` on a dynamic port. For every incoming local connection, the implementation:

- Opens a direct TCP/IP channel using `session.channel_open_direct_tcpip`
- Forwards traffic bidirectionally between the local socket and the remote database host/port
- Runs an idle-session checker that pings the SSH server every 30 seconds
- Tears down the listener automatically if the SSH session disconnects

**Robustness features** include exponential back-off reconnection logic and maximum attempt limits, ensuring transient network failures do not leave dangling listeners or orphaned database connections.

## Multi-Layer Transport Support

DBX supports complex network topologies by validating layer ordering in [`transport_layer_tunnel.rs`](https://github.com/t8y2/dbx/blob/main/transport_layer_tunnel.rs). The **`plan_transport_layers`** function builds a routing table that records the target host/port for each layer and the next downstream destination. 

**Critical constraint**: HTTP tunnels must occupy the outermost layer (closest to the client), while SSH tunnels typically sit innermost (closest to the database). This ordering ensures encrypted traffic flows correctly through proxy intermediaries.

## Lifecycle Management and Cleanup

When queries complete or connections close, DBX invokes **`stop_transport_layers`** with the same layer count used during creation. This function iterates through the SSH, proxy, and HTTP tunnel managers, terminating each listener and releasing system resources:

```rust
dbx_core::db::transport_layer_tunnel::stop_transport_layers(
    &conn.id,
    conn.effective_transport_layers().len(),
    &ssh_manager,
    &proxy_manager,
    &http_manager,
).await;

```

The cleanup process ensures no persistent port bindings remain open after the database connection terminates.

## Configuration Examples

### JSON Configuration for SSH Bastion

Define a MySQL connection routed through an SSH bastion server:

```json
{
  "name": "prod-mysql",
  "db_type": "mysql",
  "host": "127.0.0.1",
  "port": 0,
  "username": "app_user",
  "password": "",
  "transport_layers": [
    {
      "type": "ssh",
      "id": "bastion",
      "host": "bastion.example.com",
      "port": 22,
      "user": "deployer",
      "key_path": "~/.ssh/id_ed25519",
      "key_passphrase": "",
      "connect_timeout_secs": 10,
      "expose_lan": false,
      "use_ssh_agent": true,
      "ssh_agent_sock_path": ""
    }
  ]
}

```

Note that `port: 0` indicates DBX will overwrite this value with the dynamically allocated local tunnel port at runtime.

### Rust Implementation Pattern

Programmatically open a tunneled connection:

```rust
let conn = dbx_core::connection::Connection::load("prod-mysql").await?;
let (host, port) = if conn.effective_transport_layers().is_empty() {
    (conn.host.clone(), conn.port)
} else {
    let local_port = dbx_core::db::transport_layer_tunnel::start_transport_layers(
        &conn.id,
        &conn.effective_transport_layers(),
        &conn.host,
        conn.port,
        &ssh_manager,
        &proxy_manager,
        &http_manager,
    ).await?;
    ("127.0.0.1".to_string(), local_port)
};

// Connect driver through the local tunnel endpoint
let opts = mysql::Opts::from_url(&format!(
    "mysql://{}:{}@{}:{}/{}",
    conn.username, conn.password, host, port, conn.database.unwrap_or_default()
))?;

```

## Summary

- **Transport layer abstraction**: DBX implements SSH tunnels as ordered transport layers in [`transport_layer_tunnel.rs`](https://github.com/t8y2/dbx/blob/main/transport_layer_tunnel.rs), supporting chained configurations with proxies and HTTP tunnels.
- **On-demand startup**: Tunnels initialize automatically when `effective_transport_layers()` returns non-empty results, binding to local ports that database drivers use as their connection endpoints.
- **Multi-method authentication**: The [`ssh_tunnel.rs`](https://github.com/t8y2/dbx/blob/main/ssh_tunnel.rs) implementation tries public-key, password, and SSH-agent authentication sequentially using the `russh` crate.
- **Session resilience**: Automatic keep-alive checks every 30 seconds and exponential back-off reconnection prevent dropped connections from failing silently.
- **Secure credential storage**: SSH passwords and key passphrases are extracted from configuration and stored in the encrypted SQLite `connection_secrets` table via [`storage.rs`](https://github.com/t8y2/dbx/blob/main/storage.rs).

## Frequently Asked Questions

### How does DBX handle authentication when both a key file and SSH agent are configured?

DBX attempts authentication methods in a specific order: first public-key authentication using the specified `key_path`, then password authentication if configured, and finally SSH-agent authentication if `use_ssh_agent` is true. This cascade ensures compatibility with different server configurations while prioritizing key-based methods.

### Can DBX route traffic through multiple SSH hops or bastion hosts?

Yes, DBX supports ordered transport layers that can chain multiple SSH tunnels, proxies, or HTTP tunnels. The `plan_transport_layers` function in [`transport_layer_tunnel.rs`](https://github.com/t8y2/dbx/blob/main/transport_layer_tunnel.rs) validates the sequence and builds a routing table where each layer connects through the previous layer's local listener, allowing multi-hop configurations.

### What happens if the SSH connection drops while a database query is running?

The [`ssh_tunnel.rs`](https://github.com/t8y2/dbx/blob/main/ssh_tunnel.rs) implementation includes an idle-session checker that pings the SSH server every 30 seconds. If the session disappears, DBX tears down the local listener and triggers reconnection logic with exponential back-off. The database driver will receive a connection error, allowing the application to retry or fail gracefully.

### Where does DBX store SSH passwords and private key passphrases?

According to the DBX source code in [`packages/node-core/src/connections.ts`](https://github.com/t8y2/dbx/blob/main/packages/node-core/src/connections.ts) and [`crates/dbx-core/src/db/storage.rs`](https://github.com/t8y2/dbx/blob/main/crates/dbx-core/src/db/storage.rs), sensitive credentials are extracted during configuration loading and stored in an encrypted SQLite table called `connection_secrets`. The in-memory configuration object retains only non-sensitive fields, ensuring credentials are never exposed in logs or memory dumps.