不是 VPN 挡住了 psql:你的客户端连去了别处
psql 和 GUI 客户端可能从同一台机器连向不同的终点。先比较主机、套接字、角色和 TLS,再怪 VPN。
两个客户端在同一台笔记本上,走同一条隧道,却只有一个能连上。这种对比看起来像是 VPN 在挑人,但更常见的解释是两个客户端连接的终点根本不同。GUI 保存自己的主机、端口、角色和 TLS 设置,需要时悄悄建立 SSH 转发;而 psql 用 libpq 的默认值拼出连接,这些默认值你的 shell 可能早就设好了。复现能用的连接,是去读 GUI 的设置,而不是重装隧道。
迷思:一个能连的客户端就证明了通路
如果 GUI 能连上,说明隧道在传流量,这也正是 GUI 唯一能证明的事。它没有说明 psql 解析的是哪个地址、用哪个角色认证,也没有说明它打开的套接字是否会离开本机。通路是共享的,参数不是。
本地转发会加大差距。GUI 可能连的是它经 SSH 转发出来的某个端口上的 127.0.0.1,而那个监听只在 GUI 会话期间存在。指向 localhost 却没有这条转发的 psql,得到的连接被拒与 VPN 毫无关系。
读懂 GUI 实际使用的终点
打开连接属性,复制四个值:主机、端口、数据库和角色。然后找一找隧道标签页,因为 SSH 转发会把私有地址变成本地端口,也会改变该告诉 psql 的内容。
| Setting | What the GUI holds | What psql uses when the value is absent |
|---|---|---|
| Destination | A saved hostname, sometimes behind a forward | A local Unix socket, not the remote endpoint |
| Port | The direct port or a forwarded local port | 5432, or the port from a service file |
| Role and database | Stored per connection profile | Your operating-system user name |
| TLS identity | Its own CA bundle and verification mode | Whatever PGSSLMODE or the service file says |
| Startup files | Not read at all | Read unless -X is passed |
每一行失败的层次不同,你收到的报错正好点出那一层。把已保存配置的友好名称粘进 psql 不会带上上面任何一个值,而服务名要经过一个终端可能永远不会读的文件来解析。
libpq 从你的 shell 继承了什么
psql 是 libpq 之上的薄客户端,而 libpq 会先读环境。PGHOST、PGHOSTADDR、PGPORT、PGDATABASE、PGUSER、PGSERVICE 和 PGSSLMODE 这类变量每一个都能改写连接的一部分。它们是被继承而非输入的,所以你执行的命令里根本看不到。导出的 PGSSLMODE 也能在命令行不置一词的情况下改变客户端接受什么。
其中两个变量最该先被怀疑。PGHOSTADDR 提供一个绕过域名解析的数字地址,而主机名仍然决定证书身份;PGSERVICE 则从服务文件里取出主机、数据库和 TLS 选择。为了排查就把整个环境倒出来是坏习惯,因为里面可能有密码和无关令牌;不如只挑你关心的变量。
同时省略 host 和 hostaddr 会选中 Unix 域套接字,而不是网络地址。因此不带 -h 的 psql 调用可能在本机成功,也可能以套接字错误失败,却从未碰到 GUI 使用的路由。
把同样的参数手动传过去
拿到 GUI 的值之后,显式传参,并跳过可能追加覆盖的启动文件。下面的连接串只是示意:把终点、端口、数据库和角色换成管理员批准的值,并保留已经可用的信任配置。
psql -X "host=db.internal.example port=6432 dbname=orders user=report_ro sslmode=verify-full connect_timeout=5"那条命令里有三个细节是故意的。-X 会抑制 psql 的启动文件,但不会抑制 libpq 的环境或服务定义。connect_timeout=5 让黑洞路由无法把终端卡住几分钟。sslmode=verify-full 让证书和主机名校验保持开启,而这正是 TLS 报错时人们会关掉的部分。
如果批准的终点位于连接池后面,6432 端口和 5432 端口可能通过不同的服务器身份到达同一份数据,校验也可能因为连接池的名字而不是数据库的名字失败。请在连接串里改正名字,而不是削弱模式。
让报错来决定下一步
失败信息会说明尝试走到了哪一步,这比任何对隧道的猜测都可靠。解析不了的域名在套接字出现之前就失败,超时意味着路由或防火墙丢了包,拒绝意味着有东西应答并拒绝,认证错误则意味着网络本来就是通的。
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把 \conninfo 的输出与 GUI 的实时会话对照:同一主机、同一端口、同一角色、同一 TLS 状态。这四项一致时,VPN 就再没什么可解释的了。在它们一致之前,每一次额外的隧道改动都是对终端从未建立过的连接的猜测。