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.
.webp&w=3840&q=75)
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.comLeave 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 5433A 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 3Start 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.