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
localhostfrom 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:
| Field | Notes |
|---|---|
| SSH host | The bastion host's address |
| SSH port | Default 22 |
| SSH user | Your user on the bastion |
| Authentication | Password or Key |
| Database host | From the bastion's perspective — often localhost, 127.0.0.1, or an internal hostname |
| Database port | The 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:
- Host is known and key matches — connection proceeds silently.
- 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. - 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.comQueryDeck 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.comor10.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 opsIn 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]──> databaseBoth 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
- SSL/TLS modes to add encryption on top of the tunnel
- Connection troubleshooting for general connection issues