SSH tunnel to PostgreSQL from iPhone

Most production databases are not on the internet, and they shouldn't be. HeapDeck reaches them the way you already do from a laptop: through your bastion, with your key, and nothing of ours in between.

Coming soon to the App Store Email me when it's out

Free · Pro $29.99 one-time · PostgreSQL 13–18 · iOS 17+ · iPhone

Verify this server sheet: the server's SHA-256 certificate fingerprint and expiry date, shown once before HeapDeck trusts it

How the tunnel works

In a connection’s settings, turn on Use SSH tunnel and fill in the bastion’s host, port (22 by default) and user. HeapDeck opens an SSH session from your iPhone to the bastion and asks it to forward a connection to the database host and port you entered, the same direct-tcpip channel ssh -L uses. The PostgreSQL protocol then runs inside that channel, with TLS on top according to the connection’s sslmode.

Enter the database host as the bastion sees it: a private address like 10.0.0.5 works as long as the bastion can reach it.

Keys the tunnel accepts

Version 1.0 authenticates to the bastion with a password or an unencrypted Ed25519 private key in OpenSSH format, the text that starts with -----BEGIN OPENSSH PRIVATE KEY-----. The bastion’s own host key can be Ed25519 or ECDSA.

The safest setup is a key made only for HeapDeck that can do nothing but forward to the database:

ssh-keygen -t ed25519 -N "" -C heapdeck -f heapdeck_ed25519

On the bastion, add the public key to ~/.ssh/authorized_keys with options that limit it to this one forward:

restrict,port-forwarding,permitopen="10.0.0.5:5432" ssh-ed25519 AAAA… heapdeck

restrict turns off shells, agent forwarding and the rest; port-forwarding and permitopen give back exactly one destination. If the phone is ever lost, deleting that line revokes it without touching anyone else’s access.

Paste the private key into the connection. It is stored in the iOS Keychain on this iPhone only and never synced.

Host keys and certificates are pinned

The first time you connect, HeapDeck shows the bastion’s host key fingerprint and asks you to compare it with what ssh-keygen -lf prints on the bastion. Once you trust it, it is remembered; if it ever changes, HeapDeck stops with This bastion’s host key changed and connecting is the secondary choice, not the default.

The database’s TLS certificate works the same way under sslmode=require: its SHA-256 fingerprint is shown once and pinned.

When it doesn’t connect

Test connection walks the whole path, Local Network permission, SSH tunnel, TLS and login, then reports where it stopped: The tunnel is fine — this is PostgreSQL rejecting the credentials tells you not to look at SSH. Typical messages:

  • SSH tunnel failed. Check bastion host / key.: the bastion is unreachable or refused the forward.
  • SSH auth failed. Check bastion username / password / key.: wrong user, or a key that is encrypted, not Ed25519 or not in OpenSSH format.
  • SSH host key changed — possible MITM. Connection refused.: the bastion presented a different host key and you didn’t trust it.

What version 1.0 doesn’t support

RSA keys, passphrase-protected keys, keyboard-interactive and one-time codes, jump-host chains and client certificates (mTLS). If your bastion requires one of these, HeapDeck 1.0 can’t connect through it.