先交代一个背景:我平时习惯用 Java 写后端,数据库这块基本就是 PostgreSQL。前段时间搭建测试环境,新装了一台 PostgreSQL,结果本地用 psql 连过去直接报了一串乱码错误:
org.postgresql.util.PSQLException: ��������: û���������� “192.168.1.101“,乍一看一脸懵,但把乱码翻译过来,其实就是一句话:没有匹配 pg_hba.conf 的条目。这类连接失败问题在 PostgreSQL 运维里实在太常见了,尤其是第一次从远程客户端连数据库的时候,十个里面有八个都是这个原因。这篇内容我就围绕这个报错详细拆解一下,从原理到排查再到配置修复,尽量让你看完之后不光能解决手头的问题,以后遇到类似的连接问题也能有自己的判断思路。
1. 报错信息拆解:先看懂这串“天书”在说什么
1.1 PSQLException 与 pg_hba.conf 的关系
我们得先搞清楚这个报错是怎么来的。Java 程序里看不到具体中文,因为 JDBC 驱动抛出的异常类叫org.postgresql.util.PSQLException,它只是 PostgreSQL JDBC 驱动的通用异常外壳。真正有价值的信息在后面的消息文本里,也就是那串乱码。大多数情况下乱码是因为服务端返回的错误信息用了服务端本地化语言,而客户端连接串里没指定编码,Windows 控制台默认 GBK 解码 UTF-8 文本,于是显示成一堆问号。
把乱码还原之后,错误本质非常清晰:
错误: 没有匹配 pg_hba.conf 的条目这句话直接告诉我们,PostgreSQL 服务端在检查访问控制规则时,没有找到一条允许当前客户端连接的规则,于是拒绝了连接请求。换句话说,这不是密码错误,不是用户名错误,也不是数据库不存在,而是你的客户端 IP、数据库、用户名、连接方式这四者的组合,没有命中任何一条白名单规则。
很多人一看到PSQLException就怀疑是驱动问题、密码问题,实际上这时候最应该做的是打开 PostgreSQL 数据目录下的pg_hba.conf文件看一眼。pg_hba.conf就是 PostgreSQL 的访问控制白名单,全称是“host-based authentication”配置文件。所有客户端连接,不管本地还是远程,都必须匹配它里面的一条规则,否则连接就会被直接拒绝,进程都不会进入密码校验阶段。这就解释了为什么即使你密码完全正确,依然连不上。
1.2 乱码问题:客户端字符集带来的二次干扰
我之所以要多说一句乱码的事,是因为很多人第一次遇到这个报错时,会被那一串问号带偏思路,以为是什么编码不兼容引起的深层次问题。实际操作中,如果控制台显示乱码,有两个办法快速确认内容。第一个办法是看报错的最后一部分,一般会带上客户端的 IP 地址,比如“192.168.1.101“,这个 IP 就是被拒绝的来源地址。第二个办法是把客户端的编码强制改成 UTF-8,很多数据库管理工具和终端都支持设置客户端编码,psql 可以用SET client_encoding TO 'UTF8',JDBC 连接串加?characterEncoding=UTF-8。
但这里要提醒一句,改客户端编码只是让报错信息可读,并不能修饰服务端的判断逻辑。也就是说,即便你在客户端把编码调对了,看到的信息仍然是“没有匹配 pg_hba.conf 的条目”,问题不会自动消失。所以正确的姿势是:第一,确认报错是不是这个;第二,拿着报错里的 IP 去检查pg_hba.conf。如果报错里的 IP 是你自己的 IP,而配置里确实没放行,那这事基本上就定位清楚了。
2. 快速排查流程:连接失败不全是 pg_hba.conf 的锅
2.1 从网络到数据库,一步步缩小问题范围
pg_hba.conf是常见原因,但绝对不是唯一原因。远程连接 PostgreSQL 的完整链路是:客户端通过网络访问服务器 IP,服务器上的 PostgreSQL 进程监听某个端口,收到连接后校验访问控制规则,然后才校验用户名密码。这个链路里任何一环出问题,表现都是“连接失败”,但报错细节不一样。
我自己排查的时候习惯按这个顺序走:先确认网络通不通,再确认端口通不通,再确认 PostgreSQL 有没有在监听,最后才去看访问控制。如果网络都不通,你连报错都看不到,只会看到Connection timed out或者Connection refused。而PSQLException: 没有匹配 pg_hba.conf 的条目这个报错有一个特点,它意味着你已经过了网络层,成功触达了 PostgreSQL 服务端,否则根本不会进入到访问控制校验这一步。
有个小技巧,用telnet或者nc测端口最直接。比如 PostgreSQL 默认端口是 5432,可以在客户端机器上执行:
telnet 192.168.1.200 5432如果端口通了,会立即收到 PostgreSQL 的欢迎信息或者直接黑屏等待,这说明 TCP 连接是通的。如果网络不通会卡住直到超时,如果端口没监听会显示连接被拒绝。这个测法比连数据库本身更快,因为不需要输入任何用户名密码,也能排除大部分网络因素。知道这一点,你在排查的时候就不会一头扎进pg_hba.conf里瞎改。
2.2 listen_addresses 与防火墙的双重影响
网络通了之后,还有一个容易被忽略的配置项,就是postgresql.conf里的listen_addresses。这个参数控制 PostgreSQL 监听哪些网络接口。默认情况下,很多安装包把listen_addresses设成localhost,这意味着就算防火墙全开,外部 IP 也没法连进来,因为服务端根本没在外部网卡上监听。
有时候你会遇到一个很奇怪的现象:本机psql能连,远程连不上。这种情况十有八九是listen_addresses没改。检查方法很简单,在 PostgreSQL 服务器上执行:
SHOW listen_addresses;如果返回的是localhost,而你的客户端是从其他机器发起的连接,那就需要把它改成*,或者改成具体的服务器 IP 地址。修改之后需要重启 PostgreSQL 服务才能生效,这一步和pg_hba.conf的reload可不一样,listen_addresses是启动时读取的参数,不能热加载。
防火墙也是老生常谈的问题了。很多云服务器默认安全组没放行 5432 端口,或者本机防火墙拦了外部来源。判断办法就是在服务器上执行telnet 127.0.0.1 5432能通,换成服务器公网 IP 或局域网 IP 就不通,那基本就是防火墙层面的问题。这个环节容易和pg_hba.conf的问题混淆,因为两者表现非常接近,区别就在于报错细节:如果连接被防火墙拦截,客户端通常报Connection timed out;如果连接到达了 PostgreSQL 但被访问控制拒绝,报错则是我们开头看到的 PSQLException 加上 pg_hba 相关消息。学会从报错文案区分阶段,排查效率能翻一倍。
3. pg_hba.conf 配置详解:一行放行,十分钟搞定
3.1 认识 pg_hba.conf 的条目格式
聊完了排查链路,接下来落到实际的配置文件。pg_hba.conf的位置取决于操作系统和安装方式。Linux 上,如果是用 apt 安装的 PostgreSQL,通常位于/etc/postgresql/<版本号>/main/pg_hba.conf;如果是源码编译安装,或者通过 Docker 部署,一般在数据目录下,比如/var/lib/postgresql/data/pg_hba.conf。
文件里每一行非注释内容都是一条规则,格式固定为五列或六列:
连接类型 数据库 用户 地址 认证方法还有一个可选列,用来指定认证方法所需参数,一般用不到,看到最多的就是五列。我们来拆一下每一列的含义。连接类型主要有四种:
local:本机通过 Unix Socket 连接。host:通过 TCP/IP 连接,不管是本机还是远程。hostssl:仅允许 SSL 加密的 TCP/IP 连接。hostnossl:仅允许非 SSL 的 TCP/IP 连接。
数据库列可以写具体数据库名、用逗号分隔多个数据库名,也可以用all表示所有数据库。用户列同理,可以用all表示所有用户。地址列比较特殊,它只在host类型中有效,写法支持 IPv4 地址、IPv6 地址、CIDR 网段,比如192.168.1.0/24表示整个 B 段子网,也可以用0.0.0.0/0表示所有 IPv4 地址。认证方法这一列是核心,决定这个连接怎么验证身份,常见的有trust、reject、peer、md5、scram-sha-256等。
文件里的规则是从上到下顺序匹配的,匹配到第一条就不再看后面的规则了。也就是说,文件的顺序很重要,匹配优先权完全由行号决定。默认安装的 PostgreSQL 里,文件末尾往往有一条host all all 0.0.0.0/0 scram-sha-256之类的规则,如果前面的规则里没有你的组合,最终会落到这条上面。如果这条规则用的是reject,那不好意思,直接拒绝。如果这条规则存在但认证方法不匹配,那也会报认证失败。理解这个顺序逻辑后,你就知道为什么有时候明明后面放行了某种连接,前面一条更宽的规则反而先拦截了。
3.2 常见认证方法选型:trust、peer 与 scram-sha-256
认证方法这块是很多人容易踩坑的地方。不同认证方法对应的登录方式和安全性差异非常大,选错了要么导致连不上,要么导致数据库裸奔。
trust表示完全信任,只要匹配这条规则的客户端连接,不用输密码就能登录。这个认证方法只适合本地调试,或者完全可信的内网环境,生产环境千万别用。有人为了图省事,把本机所有连接都配成trust,结果数据库被局域网里其他人一个psql -U postgres就进去了,数据随便看,这是非常危险的操作。
peer是 Linux 下本机 Socket 连接专用的认证方式,它通过获取操作系统用户名来匹配数据库用户名,同样不需要密码。比如你用系统用户postgres执行psql,它会直接用postgres这个数据库角色登录,前提是系统用户和数据库角色一致。这也是为什么很多人在服务器上用sudo -u postgres psql能直接进入数据库的原因。但peer只适用于local类型,远程 TCP 连接不能用。
md5和scram-sha-256都是密码认证方式,区别在于密码存储和传输的加密算法。md5是旧版算法,存在已知的安全缺陷,PostgreSQL 14 之后的默认密码加密方式已经改成了scram-sha-256。如果你的数据库用户密码是用scram-sha-256算法存储的,但pg_hba.conf里配的是md5,认证就会失败,反过来也一样。这里有一个常见误区:看到密码错误,其实不一定是密码本身错了,而是密码版本和认证方法不一致。可以用下面这个 SQL 检查用户密码的存储方式:
SELECT rolname, rolpassword FROM pg_authid;rolpassword字段的开头会标明算法,如果是SCRAM-SHA-256$开头,说明这个用户的密码是 SCRAM 版本;如果是md5开头,则是 MD5 版本。新安装的 PostgreSQL 一般建议统一使用scram-sha-256。
3.3 配置示例与生效方式
看几个实际配置的例子。假设我的 PostgreSQL 服务器 IP 是192.168.1.200,客户端 IP 是192.168.1.101,本地调试用 Unix Socket,远程连接需要密码登录。那pg_hba.conf里可以这样写:
# 本机 Socket 连接,用 peer 认证,方便管理员本地登录 local all all peer # 本机 TCP/IP 连接,使用 scram 密码认证 host all all 127.0.0.1/32 scram-sha-256 # 局域网内网段放行,允许 192.168.1.0 整个网段通过密码登录 host all all 192.168.1.0/24 scram-sha-256 # 其他来源全部拒绝 host all all 0.0.0.0/0 reject注意最后一条,我并没有完全删掉默认的0.0.0.0/0规则,而是把认证方法设置成reject,相当于一个兜底策略,防止未来不小心把整个 PostgreSQL 暴露出去。这个习惯在生产环境非常有价值。如果直接写成host all all 0.0.0.0/0 scram-sha-256放行全部来源,万一服务器有公网 IP,你的 PostgreSQL 就会面向整个互联网提供密码认证服务,密码爆破风险会显著上升。
修改完pg_hba.conf后,不需要重启 PostgreSQL,只需要重新加载配置文件。两条常用命令任选其一:
# 方式一:通过 SQL 热加载 SELECT pg_reload_conf(); # 方式二:通过系统服务 systemctl reload postgresql执行完 reload 之后,新连接会按照新的规则进行校验。已经建立的连接不会受影响,这点在维护生产环境时很关键,你可以放心地修改配置再 reload,不用担心正在跑的业务连接一瞬间全部断掉。不过 reload 之后如果有连接失败,要及时看日志确认新的规则是否生效,别改完了没加载,空欢喜一场。
4. 完整实操复盘:从报错到恢复的完整过程
4.1 场景设定与问题复现
为了让你更直观地理解整个排查和修复过程,我模拟一个实际场景。服务器是 Ubuntu 系统,PostgreSQL 16 通过 apt 安装,服务器 IP 是192.168.1.200。我的开发机是 Windows,上面装了一个 DBeaver,准备连接服务器上的 PostgreSQL 做开发。第一次连接时,DBeaver 直接弹出了与开篇类似的错误,只是在 GUI 里显示得更完整一点:
org.postgresql.util.PSQLException: 致命错误: 没有匹配 pg_hba.conf 的条目,at "192.168.1.101", ...这个报错里的192.168.1.101就是我开发机的局域网 IP。报错信息已经把问题定位得很清楚了,但我还是按照前面的排查流程做一遍,确保不是其他问题导致误报。
4.2 逐步排查与配置修复
第一步,我在 cmd 里执行telnet 192.168.1.200 5432,确认 TCP 端口通不通。如果这里不通,后面所有步骤都没有意义。在我这个场景里,telnet 立即连上了,说明网络和端口层面没问题。这个结果和报错细节也互相印证:既然报错信息来自 PostgreSQL 服务端,那么连接肯定到了数据库。
第二步,我 SSH 登录到服务器,执行psql -U postgres看本地是否能连。这里要注意,本机 Socket 连接走的是local规则,能连不代表远程能连,但能确认 PostgreSQL 服务本身是健康的。我执行了一下,提示要输入密码,输入密码后进去了,说明服务端进程正常,用户密码也正确。
第三步,查看当前的listen_addresses:
SHOW listen_addresses;返回结果是localhost。到这里,远程连接失败的根源基本就水落石出了。即便pg_hba.conf里放行了远程 IP,listen_addresses没监听外部接口,照样连不上。我先修改postgresql.conf,把listen_addresses改成'*',表示监听所有网络接口。这个参数需要重启服务,我执行了systemctl restart postgresql。
第四步,重启完再去编辑pg_hba.conf。我打开文件后看了一下,默认配置里已经有一条host all all 127.0.0.1/32 scram-sha-256,但确实没有针对局域网 IP 的规则。我在文件末尾加了一行:
host all all 192.168.1.0/24 scram-sha-256这里我放行的不是单个 IP,而是整个局域网网段192.168.1.0/24。如果只放行自己这台开发机,下次换个机器又得改配置,所以在可信内网里用网段是更合理的做法。关于postgresql.conf的位置,Ubuntu 上可以通过SHOW config_file;查询,这一点非常方便。
第五步,执行SELECT pg_reload_conf();加载新的访问控制规则,然后回到 Windows 开发机重新用 DBeaver 连接。这次连接成功了,不再报任何异常。整个过程从排查到修复,大概十分钟不到,但付出的时间换来的是对整个 PostgreSQL 连接机制更清晰的认识。
4.3 开发环境推荐配置模板
如果你也在本地开发环境折腾 PostgreSQL,可以参考我下面的配置模板,它兼顾了本机管理和局域网内开发连接的需求:
# 本机 Socket 连接,管理员免密登录 local all postgres peer local all all scram-sha-256 # 本机 TCP 连接 host all all 127.0.0.1/32 scram-sha-256 host all all ::1/128 scram-sha-256 # 局域网开发网段 host all all 192.168.1.0/24 scram-sha-256 # 其余来源一律拒绝 host all all 0.0.0.0/0 reject host all all ::/0 reject注意我在最后加上了 IPv6 的兜底规则::/0,防止别人通过 IPv6 地址绕过限制。很多人在配置时只关注 IPv4,IPv6 的坑往往要过好久才被发现。关于 IPv6 还有一个细节:如果你在pg_hba.conf里放行了::1/128,而客户端连接时用的是localhost,系统可能解析成 IPv6 地址走::1这条规则,也可能解析成 IPv4 走127.0.0.1这条规则,很容易造成“刚才还能连,为什么现在报没有匹配的条目”的困惑。排查时多看一眼连接串里实际解析出的 IP,能少走很多弯路。
5. 常见问题排查与避坑技巧
5.1 高频问题速查表
把常见的 PostgreSQL 连接失败问题整理成一个速查表,方便你之后直接对照排查。
| 报错特征 | 常见原因 | 解决方法 |
|---|---|---|
没有匹配 pg_hba.conf 的条目 | 客户端 IP/库/用户组合未放行 | 修改pg_hba.conf添加对应规则 |
密码认证失败 | 密码错误或认证方法不匹配 | 检查密码,或用ALTER USER重置 |
Connection refused | PostgreSQL 未启动或端口未监听 | 检查服务状态和listen_addresses |
Connection timed out | 防火墙拦截或网络不通 | 检查安全组、iptables、物理网络 |
SSL connection required | 服务端强制 SSL 但客户端没启用 | 开启 SSL 或在连接串加sslmode=require |
角色不存在 | 连接串用户名有误 | 确认pg_user里是否存在该角色 |
数据库不存在 | 连接串数据库名有误 | 确认库名,可用\l查看数据库列表 |
这张表里的问题我都实际遇到过,尤其是“SSL connection required”那个,PostgreSQL 某些发行版默认配置会强制 SSL,客户端 JDBC 连接串如果没有额外设置,可能会因为 SSL 协商失败被拒。解决办法是在 JDBC 连接串里加上sslmode=require或sslmode=disable,具体取决于你对加密的需求。如果只是开发环境,sslmode=disable也能凑合。
5.2 三个最容易被忽略的细节
第一个细节是修改完配置忘记 reload。我见到太多人在pg_hba.conf里加了好几条规则,客户端还是报同样的错,最后发现配置文件里的新规则根本没生效。记住:pg_hba.conf和postgresql.conf的生效方式不一样,前者是 reload,后者如果是listen_addresses这种参数还得重启。如果不确定改的是哪个参数,最省事的办法是看 PostgreSQL 官方文档中的参数分类,或者干脆执行systemctl restart postgresql一了百了,代价是短暂中断所有连接,不建议在生产环境这么做。
第二个细节是认证方法和密码存储算法的匹配问题。PostgreSQL 14 之后,scram-sha-256成为默认密码加密方式,很多老配置里还是md5,导致客户端用正确密码连接却提示认证失败。这个时候有两种解决路径:把pg_hba.conf里的认证方法改成scram-sha-256,或者把用户的密码重新设置一次,让密码存储格式与认证方法一致。这里有个小技巧,你可以先查一下pg_authid.rolpassword的前缀,再对照pg_hba.conf的认证方法列,两者对不上就改。
第三个细节是规则顺序。pg_hba.conf是自上而下匹配的,第一条匹配成功就不再往下看。我之前有过一次配置,前面的规则写了host all all 0.0.0.0/0 reject,后面才写host all all 192.168.1.0/24 scram-sha-256,结果局域网连接一直失败。原因是0.0.0.0/0这条规则先匹配了所有来源,直接拒绝,根本轮不到后面放行规则的判断。文件的顺序逻辑就相当于代码里的 if-else,后面的条件永远只在前面的条件不满足时才生效。理解了这一点,你在编写规则时就要把更具体的网段放在前面,把宽松的兜底规则放在后面。
5.3 生产环境安全建议
最后给生产环境提几点建议。第一,不要轻易使用trust认证,除非你的服务器处于完全隔离的内网,且你清楚这样做的风险。第二,pg_hba.conf的最小化授权原则是:能精确到 IP 就不要用网段,能精确到数据库就不要用all,能精确到用户就不要放开所有角色。第三,远程连接必须使用scram-sha-256,如果有条件,建议开启 SSL 连接。第四,兜底规则一律用reject,即使是在内网环境,也不要配置成对全网开放密码认证。
生产环境的 PostgreSQL 被扫描爆破是常态,如果你在云服务器上运行数据库,安全组只放行需要访问数据库的业务服务器 IP,而不是对所有人开放 5432 端口。这样即使pg_hba.conf配置稍有疏忽,云平台层面的网络隔离也能起到第二道防线的作用。
说实话,没有匹配 pg_hba.conf 的条目这个报错我第一次遇到时,也是围着密码和驱动转了好几圈。后来把 PostgreSQL 的连接流程从头到尾捋了一遍,发现所有问题都逃不过一个逻辑:先网络,再监听,再访问控制,最后才是认证。只要按照这个链路逐层排查,大部分连接失败问题都能在几分钟内解决。希望这篇内容对你也有同样的帮助。