Ambos clientes están en el mismo portátil, cruzan el mismo túnel y solo uno de ellos se conecta. Esa comparación parece una prueba de que la VPN es selectiva, cuando la explicación mucho más común es que los dos clientes marcan destinos distintos. Una GUI guarda su propio host, puerto, rol y ajustes de TLS y construye en silencio un reenvío SSH cuando lo necesita, mientras que psql arma una conexión a partir de los valores predeterminados de libpq que tu shell quizá ya haya establecido. Reproducir la conexión que funciona es cuestión de leer los ajustes de la GUI, no de reinstalar el túnel.

El mito: un cliente que funciona demuestra la ruta

Si la GUI se conecta, el túnel transporta tráfico, y eso es lo único que demuestra la GUI. No dice nada sobre qué dirección resuelve psql, con qué rol se autentica ni si el socket que abre llega a salir de la máquina. La ruta es compartida; los parámetros no.

Un reenvío local amplía la diferencia. La GUI puede conectarse a 127.0.0.1 en un puerto que ha reenviado por SSH, y ese receptor solo existe mientras dura la sesión de la GUI. psql apuntado a localhost sin el reenvío recibe una conexión rechazada que no tiene nada que ver con la VPN.

Lee el extremo que la GUI usa de verdad

Abre las propiedades de la conexión y copia cuatro valores: host, puerto, base de datos y rol. Luego busca una pestaña de túnel, porque un reenvío SSH convierte una dirección privada en un puerto local y cambia lo que hay que decirle a psql.

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

Cada fila falla en una capa distinta, y el mensaje que recibes nombra esa capa. Pegar el nombre amigable del perfil guardado en psql no aporta ninguno de los valores anteriores, y un nombre de servicio se resuelve mediante un archivo que la terminal quizá nunca lea.

Qué hereda libpq de tu shell

psql es un cliente ligero sobre libpq, y libpq lee el entorno antes de leer cualquier otra cosa. Variables como PGHOST, PGHOSTADDR, PGPORT, PGDATABASE, PGUSER, PGSERVICE y PGSSLMODE pueden redirigir cada una parte de la conexión. Se heredan en lugar de escribirse, así que nada en el comando que ejecutaste las muestra. Una PGSSLMODE exportada también puede cambiar lo que acepta el cliente sin una palabra en la línea de comandos.

Dos de esas variables merecen sospecha primero. PGHOSTADDR aporta un destino numérico que anula la resolución de nombres mientras el nombre de host sigue rigiendo la identidad del certificado, y PGSERVICE saca host, base de datos y opciones de TLS de un archivo de servicio. Volcar todo el entorno para depurar esto es una mala costumbre, ya que puede contener contraseñas y tokens no relacionados; selecciona mejor las variables que te importan.

Omitir tanto host como hostaddr selecciona un socket de dominio Unix en lugar de una dirección de red. Una llamada a psql sin -h puede por tanto tener éxito localmente o fallar con un error de socket, sin tocar nunca la ruta que usa la GUI.

Envía los mismos parámetros a mano

Con los valores de la GUI en la mano, pásalos explícitamente y salta el archivo de inicio que podría añadir anulaciones. La cadena de conexión de abajo es ilustrativa: sustituye el extremo, el puerto, la base de datos y el rol por los valores que aprobó tu administrador, y conserva la configuración de confianza que ya funciona.

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

Tres detalles de ese comando son deliberados. -X suprime el archivo de inicio de psql, aunque no suprime el entorno de libpq ni una definición de servicio. connect_timeout=5 evita que una ruta bloqueada cuelgue la terminal durante minutos. sslmode=verify-full mantiene activadas las comprobaciones de certificado y de nombre de host, que es justo la parte que la gente desactiva cuando aparece un error de TLS.

Si el extremo aprobado está detrás de un pooler, el puerto 6432 y el puerto 5432 pueden llegar a los mismos datos mediante identidades de servidor distintas, y la verificación puede fallar por el nombre del pooler en lugar del de la base de datos. Corrige el nombre en la cadena de conexión en vez de debilitar el modo.

Deja que el mensaje de error elija el siguiente paso

El texto del fallo dice hasta dónde llegó el intento, y eso supera cualquier conjetura sobre el túnel. Un nombre que no se resuelve falla antes de que exista un socket, un tiempo de espera significa que la ruta o un cortafuegos descartó paquetes, un rechazo significa que algo respondió y se negó, y un error de autenticación significa que la red ya funcionaba.

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

Compara la salida de \conninfo con la sesión en vivo de la GUI: mismo host, mismo puerto, mismo rol, mismo estado de TLS. Cuando esos cuatro coinciden, la VPN no tiene nada más que explicar. Hasta que coincidan, cada cambio adicional en el túnel es una conjetura sobre una conexión que la terminal nunca hizo.

Artículos relacionados