Connect to PostgreSQL Through an SSH Tunnel

Ghazi · July 30, 2026

An SSH tunnel lets a PostgreSQL client on your Mac reach a database that is visible from a bastion host but not exposed to the public internet. The local client sends traffic to a loopback port, and SSH forwards it to the database from the remote side.

Use PostgresGUI's built-in SSH section, or run ssh -N -T -L 127.0.0.1:5433:DB_HOST:5432 USER@BASTION. With the shell tunnel, connect PostgreSQL to 127.0.0.1 on port 5433. Keep host-key checking enabled.

PostgresGUI connection form with SSH tunnel settings
The built-in tunnel keeps the SSH credentials and database credentials in separate sections.

Open a local tunnel from Terminal

ssh -N -T   -o ExitOnForwardFailure=yes   -o ServerAliveInterval=60   -o ServerAliveCountMax=3   -L 127.0.0.1:5433:db.internal.example:5432   deploy@bastion.example.com

Leave this process running. The database hostname is resolved from the bastion side.

Connect through the local end

psql 'postgresql://DB_USER@127.0.0.1:5433/DB_NAME'

Use the database user here, not the SSH user.

Keep the two logins separate

The SSH server authenticates your Mac to the bastion. PostgreSQL then authenticates a database role after the tunnel is open. Those usernames and passwords are often different.

  • SSH host: bastion.example.com
  • SSH user: deploy or another operating-system account
  • Database host: the hostname reachable from the bastion
  • Database user: a PostgreSQL role

Use the built-in PostgresGUI tunnel

Create a connection and enable SSH Tunnel. Enter the bastion host, SSH port, SSH username, and either a password or private key. In the database section, keep the real private database host and port.

PostgresGUI opens the forwarding socket for the saved connection, then closes it when the database session ends. See the PostgreSQL GUI for Mac workflow for the rest of the client.

Use a shell tunnel when you need to inspect it

The -L value is local-address:local-port:database-host:database-port. Binding explicitly to 127.0.0.1 prevents other machines on your network from using the forwarded port.

The -N and -T flags skip a remote command and terminal allocation. ExitOnForwardFailure makes ssh stop immediately when it cannot establish the requested forward.

Check whether the local port is listening

nc -vz 127.0.0.1 5433

A listening local port proves the forward exists, not that the PostgreSQL credentials are correct.

Put repeatable settings in SSH config

A named SSH host keeps the identity file, username, and forwarding options out of a long command. It also reduces the chance of forwarding to the wrong database.

~/.ssh/config

Host production-db-tunnel
  HostName bastion.example.com
  User deploy
  IdentityFile ~/.ssh/id_ed25519
  LocalForward 127.0.0.1:5433 db.internal.example:5432
  ExitOnForwardFailure yes
  ServerAliveInterval 60
  ServerAliveCountMax 3

Start it with: ssh -N -T production-db-tunnel

Diagnose the failure in order

Test each boundary separately. That keeps an SSH-key problem from looking like a PostgreSQL-password problem.

  • Run ssh -v USER@BASTION and verify the expected host key and identity.
  • From the bastion, confirm the private database host resolves and port 5432 is reachable.
  • On the Mac, confirm the chosen local port is not already in use.
  • Connect to 127.0.0.1 and the forwarded port with the database credentials.
  • If PostgreSQL TLS is required beyond the bastion, keep the provider's SSL settings enabled.