QueryDeck Docs
Connections

SSH tunnels

Connect to databases behind a bastion host with password or key-based SSH authentication.

For: developers.

When your database isn't directly reachable from your laptop (private subnet, VPN-only, behind a bastion), QueryDeck opens an SSH tunnel for you. No external ssh process, no port-forward to manage. The tunnel is part of the connection.

When you need a tunnel

You need an SSH tunnel if any of these are true:

  • The database has no public IP (private subnet, VPC).
  • The database is locked to a specific security group that only includes the bastion.
  • You have to be on a VPN or jump host to reach the database hostname.
  • The database is on localhost from the perspective of a remote server.

If you can psql directly from your laptop with the same credentials, you don't need a tunnel.

Enable the tunnel

In the connection editor, scroll to the SSH section and toggle it on. The fields appear:

FieldNotes
SSH hostThe bastion host's address
SSH portDefault 22
SSH userYour user on the bastion
AuthenticationPassword or Key
Database hostFrom the bastion's perspective — often localhost, 127.0.0.1, or an internal hostname
Database portThe database's actual port, e.g., 5432

When the tunnel is on, the top-level Host and Port fields in the connection editor still refer to the bastion. QueryDeck binds the bastion to a local port and connects to it.

Authentication

Password

Type the bastion password in the editor. It's stored in the macOS Keychain alongside your database password.

Key-based

Pick a private key file. Standard locations:

  • ~/.ssh/id_ed25519 (recommended for new keys)
  • ~/.ssh/id_rsa
  • ~/.ssh/id_ecdsa

If your key is passphrase-protected, QueryDeck prompts for the passphrase the first time. It can be saved in the macOS Keychain so you don't see the prompt again.

SSH agent

If you have ssh-agent running with your keys loaded, QueryDeck can use it directly. Pick "Use SSH agent" in the authentication dropdown. This is the cleanest setup if you already manage keys with the system agent.

Known hosts

The first time you connect to a bastion, QueryDeck checks the host key against ~/.ssh/known_hosts. Three outcomes:

  1. Host is known and key matches — connection proceeds silently.
  2. Host is new — QueryDeck shows the fingerprint and asks you to confirm. If you accept, the key is added to known_hosts. If you decline, the connection is aborted.
  3. Host is known but the key has changed — QueryDeck refuses to connect and shows the warning. This usually means one of:
    • The bastion was rebuilt and got a new host key (benign).
    • You're being MITM'd (not benign).

To resolve a key mismatch, verify with the bastion's owner what the right fingerprint is. If the change is legitimate, remove the old entry from known_hosts and reconnect.

ssh-keygen -R bastion.example.com

QueryDeck will treat the host as new on the next attempt.

Database host from the bastion

The fields under the SSH section describe how the bastion sees the database:

  • If the bastion runs the database itself, use localhost.
  • If the bastion is in the same VPC as a managed database, use the private endpoint (e.g., db.internal.example.com or 10.0.5.42).
  • If the bastion is in a corporate network with a DNS server, use the internal hostname.

You can verify this from the bastion before configuring QueryDeck:

ssh bastion.example.com
psql -h <db host from bastion> -U <user> <database>

If that command works on the bastion, the same fields work in QueryDeck.

Two-hop tunnels

QueryDeck supports one bastion at a time. If your database is behind two SSH hops, set up the inner hop in your SSH config (~/.ssh/config) with ProxyJump:

Host inner-bastion
    HostName 10.0.99.10
    ProxyJump outer-bastion

Host outer-bastion
    HostName bastion.example.com
    User ops

In QueryDeck, configure the SSH host as inner-bastion. The system SSH config handles the proxy jump.

Tunnel lifecycle

The tunnel opens when you connect and stays open as long as the database session is active. When you disconnect, the tunnel closes.

If the SSH connection drops mid-session (Wi-Fi blip, bastion restart), QueryDeck attempts to reconnect once. If reconnection fails, you'll see a "connection lost" toast and the connection icon goes red. Reopen the connection to re-establish.

SSL inside an SSH tunnel

You can use SSL on the database leg of the tunnel. QueryDeck handles this automatically — set both the SSH tunnel and the SSL mode independently. The stack is:

QueryDeck  ──[SSH tunnel]──>  bastion  ──[SSL+TCP]──>  database

Both layers are encrypted with their own keys.

Performance

A tunnel adds about 1–5ms of latency depending on the bastion. For interactive querying, you won't notice. For bulk operations (large exports, schema dumps), expect somewhat slower throughput than a direct connection.

Common errors

connection refused on the bastion

The bastion isn't accepting SSH on the configured port. Verify with:

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

permission denied (publickey)

The key you picked isn't authorized on the bastion. Either pick a different key, add yours to ~/.ssh/authorized_keys on the bastion, or use password auth.

local port already in use

QueryDeck binds a local port for the tunnel. If it's already taken, the connection fails. Disconnect any other connection sharing the bastion, or restart QueryDeck.

Database host unreachable from bastion

You configured the SSH tunnel correctly, but the database host you specified isn't reachable from the bastion. SSH into the bastion manually and check.

What's next