Both clients sit on the same laptop, cross the same tunnel, and only one of them connects. That comparison feels like proof the VPN is selective, when the far more common explanation is that the two clients dial different destinations. A GUI keeps its own host, port, role and TLS settings and quietly builds an SSH forward when it needs one, while psql assembles a connection from libpq defaults your shell may already have set. Reproducing the working connection is a matter of reading the GUI's settings, not of reinstalling the tunnel.

The Myth: One Working Client Proves the Path

If the GUI connects, the tunnel carries traffic, and that is the only thing the GUI proves. It says nothing about which address psql resolves, which role it authenticates as, or whether the socket it opens ever leaves the machine. The path is shared; the parameters are not.

A local forward widens the gap. The GUI may connect to 127.0.0.1 on a port it forwarded over SSH, and that listener exists only for the life of the GUI session. psql pointed at localhost without the forward gets a refused connection that has nothing to do with the VPN.

Read the Endpoint the GUI Actually Uses

Open the connection's properties and copy four values: host, port, database and role. Then look for a tunnel tab, because an SSH forward turns a private address into a local port and changes what psql has to be told.

SettingWhat the GUI holdsWhat psql uses when the value is absent
DestinationA saved hostname, sometimes behind a forwardA local Unix socket, not the remote endpoint
PortThe direct port or a forwarded local port5432, or the port from a service file
Role and databaseStored per connection profileYour operating-system user name
TLS identityIts own CA bundle and verification modeWhatever PGSSLMODE or the service file says
Startup filesNot read at allRead unless -X is passed

Each row fails at a different layer, and the message you get names that layer. Pasting the friendly name of the saved profile into psql carries none of the values above, and a service name resolves through a file the terminal may never read.

What libpq Inherits From Your Shell

psql is a thin client over libpq, and libpq reads the environment before it reads anything else. Variables such as PGHOST, PGHOSTADDR, PGPORT, PGDATABASE, PGUSER, PGSERVICE and PGSSLMODE can each redirect part of the connection. They are inherited rather than typed, so nothing in the command you ran shows them. An exported PGSSLMODE can also change what the client accepts without a word on the command line.

Two of those variables deserve suspicion first. PGHOSTADDR supplies a numeric destination that overrides name resolution while the hostname still governs certificate identity, and PGSERVICE pulls host, database and TLS choices out of a service file. Dumping the whole environment to debug this is a bad habit, since it can contain passwords and unrelated tokens; select the variables you care about instead.

Omitting both host and hostaddr selects a Unix-domain socket rather than a network address. A psql invocation with no -h can therefore succeed locally, or fail with a socket error, without ever touching the route the GUI uses.

Send the Same Parameters by Hand

With the GUI's values in hand, pass them explicitly and skip the startup file that could add overrides. The connection string below is illustrative: replace the endpoint, port, database and role with the values your administrator approved, and keep the trust configuration that already works.

psql -X "host=db.internal.example port=6432 dbname=orders user=report_ro sslmode=verify-full connect_timeout=5"

Three details in that command are deliberate. -X suppresses psql's startup file, though it does not suppress libpq's environment or a service definition. connect_timeout=5 keeps a black-holed route from hanging the terminal for minutes. sslmode=verify-full keeps certificate and hostname checks switched on, which is exactly the part people disable when a TLS error appears.

If the approved endpoint sits behind a pooler, port 6432 and port 5432 may reach the same data through different server identities, and verification can fail on the pooler's name rather than the database's. Correct the name in the connection string instead of weakening the mode.

Let the Error Message Choose the Next Step

The failure text says how far the attempt got, which beats any guess about the tunnel. A name that will not resolve fails before a socket exists, a timeout means the route or a firewall dropped packets, a refusal means something answered and declined, and an authentication error means the network already worked.

psql -X -w "host=db.internal.example port=6432 dbname=orders user=report_ro sslmode=verify-full connect_timeout=5" -c '\conninfo'
env | grep -E '^PG'
psql --version

Compare the \conninfo output with the GUI's live session: same host, same port, same role, same TLS state. When those four agree, the VPN has nothing left to explain. Until they agree, every extra tunnel change is a guess about a connection the terminal never made.

সম্পৰ্কিত প্ৰবন্ধ