Why ExitOnForwardFailure=yes does not prove your database is reachable
An SSH tunnel can be listening on your laptop while the service behind it is unavailable. ExitOnForwardFailure=yes is useful, but it does not turn the SSH process into a database health check. This distinction matters when a PostgreSQL client says “connection failed” even though ssh -N is still running. Before changing credentials, separate the local listener, the SSH transport, the destination…
An SSH tunnel can be running on your laptop while the service on the remote end is not reachable. The ExitOnForwardFailure=yes setting in SSH does not automatically check if the destination database is reachable. This is important to remember when a PostgreSQL client reports a connection failure, even though the SSH tunnel is still active.
To demonstrate this, a small test was conducted on September 17, 2026, using a loopback-only setup with macOS, OpenSSH 10.3p1, Node.js 25.6.1, and ssh2 1.17.0. The test used the system OpenSSH client, an in-process SSH server, and a minimal HTTP target. The SSH client had local forwarding enabled with the ExitOnForwardFailure=yes option.
During the test, the SSH server tried to connect to a closed loopback port, which the operating system responded with ECONNREFUSED. Despite this, the local TCP connection to the forwarding port succeeded, and the SSH process remained running. When the HTTP target was started, a new request through the existing tunnel returned the expected result.
Starting a second SSH process with the same local listening port resulted in an "Address already in use" error, causing the second process to exit with code 255. This test shows that the failure to connect to the destination is a separate issue from the SSH tunnel's forwarding listener.
For a database reachable through an SSH gateway, you can test the connection with a command like: ssh -N -T -o ExitOnForwardFailure=yes -L 127.0.0.1:15432:db.internal:5432 developer@gateway.example. Replace the example names with your actual connection details, verify the host key, and keep the process running. The database client should connect to 127.0.0.1 on port 15432, while the SSH connection on the gateway resolves to db.internal on port 5432.
The test reveals that the layer failing should be investigated, such as SSH authentication, key selection, and server authentication policies. Check for an address already in use on the Mac, choose a different unused local port, and update the database client accordingly. If the forwarding destination is set to the loopback address on the Mac, that connection originates in the SSH server's network context, not your local machine.
After the local TCP connection succeeds, a failure in the forwarding channel may indicate issues with the destination hostname, port, service listener, routing, or forwarding policy on the SSH side. The PostgreSQL server may report an authentication error, and you should inspect the database identity and authentication rules. TLS certificate verification failures should also be investigated, as the connection reached the PostgreSQL server.
Disabling verification to make the error disappear is not recommended, as it would not verify the database identity or authentication. Use your normal database client with the appropriate credentials and TLS settings for the final check. SSH authentication and database authentication are separate processes, and database TLS may still be required even if the SSH encryption ends at the gateway.
Written by urgent.news from Dev.to's reporting — not their text. Machine-written — may contain errors; check the original before relying on it.