如果你第一次在生产环境看到下面这行报错:
FATAL: no pg_hba.conf entry for host "10.10.2.18", user "app_user", database "app_db"第一反应多半是"数据库挂了"或者"网络不通"。其实 PostgreSQL 运行得好好的,只是连接握手阶段,服务器端按 pg_hba.conf 里的规则对这次"来源地址 + 用户名 + 数据库 + 连接方式"做了准入判断,没有找到可以放行的条目,于是直接拒绝。pg_hba.conf 全称 host-based authentication configuration,是 PostgreSQL 客户端认证的第一道闸门,也是大多数连接问题的真正源头。这篇文章我会从文件原理、逐列语法、认证方法选择、场景配置、排查链路一路讲到底,适合刚接触 PostgreSQL 的开发者也适合被远程连接困扰的运维。读完之后你再看到类似报错,基本能三分钟定位问题。
1. 一条连接请求被拦下时,PostgreSQL 到底在做什么
1.1 配置文件的作用与读取时机
pg_hba.conf 控制的是"谁、从哪儿来、用哪种方式、能访问哪个库"。它不像表权限或者角色权限那样在 SQL 层执行,而是在客户端连接建立之前就被检查。服务器启动或者收到 reload 信号时,PostgreSQL 会把整个文件读入内存,后续每个新连接过来,都会拿着当前的规则集合做匹配判断。
文件默认位于数据目录下,也就是$PGDATA/pg_hba.conf。但这个路径不一定总在预期位置,尤其是用发行版自带安装包、或者一个机器上跑多个实例的时候。最靠谱的办法是用 psql 查看当前实例实际加载的位置:
SHOW hba_file;我遇到过有人在/etc/postgresql/15/main/pg_hba.conf里改了半天规则,连接行为一点变化都没有,最后发现实例数据目录在另一块磁盘上,hba_file指向的是另一个路径。所以无论什么时候动这个文件,先执行一遍SHOW hba_file;确认没改错文件。
修改完文件后不需要重启数据库。执行 reload 即可让新规则生效,已有连接不受影响。三种 reload 方式任选其一:
pg_ctl reload -D $PGDATA systemctl reload postgresql或者在 psql 里直接:
SELECT pg_reload_conf();1.2 匹配顺序才是真正的"规则"
pg_hba.conf 的匹配逻辑是自上而下逐条扫描,只要匹配到第一条满足条件的规则,就立即采用这条规则里指定的认证方法,后面的规则不再看。这非常像机场安检:过了第一道闸口,后面就不会再把你重新拉回去排队。
正因为是"最早匹配生效",很多你以为写对了的配置其实是废的。举个例子:
host all all 10.0.0.0/24 trust host all all 10.0.0.0/24 scram-sha-256如果一个客户端恰好来自10.0.0.0/24网段,第一条规则直接命中,认证方式就是 trust,密码根本不会被校验。第二条看着写得很安全,但在连接到达它之前就已经被放行了。
所以配置这个文件的唯一心法就是:具体且严格的规则放前面,宽松或兜底的规则放后面。把reject或者0.0.0.0/0这类全量匹配放在文件末尾,避免它提前吞掉所有更精确的规则。
2. 逐列拆解 pg_hba.conf 的语法,配实例讲明白
一行完整的规则大致长这样:
host app_db app_user 192.168.1.0/24 scram-sha-256五个字段依次是连接类型、数据库名、用户名、客户端地址、认证方法。个别认证方法后面还会跟认证选项列。下面逐个拆开看。
2.1 第一列:连接类型
连接类型决定这条规则对哪类"连接通道"生效:
local:Unix socket 连接,也就是本地用 psql 不带-h参数时走的那条路。host:TCP/IP 连接,SSL 加密和不加密都算。hostssl:只匹配启用了 SSL 的 TCP 连接。hostnossl:只匹配没有用 SSL 的 TCP 连接。hostgssenc/hostnogssenc:按 GSSAPI 加密是否启用区分,通常在有 Kerberos 的环境才用。hostrepl:专门匹配流复制连接,能精确区分普通查询连接和复制协议连接。
local规则没有地址字段,因为 socket 连接的"来源地址"没有意义。其余类型都必须写客户端地址列。我在本地开发机最常用的三条规则是:
local all all peer host all all 127.0.0.1/32 scram-sha-256 host all all ::1/128 scram-sha-256socket走 peer 映射,localhost 走密码认证,互不干扰,也不会出现"本地连不上"或者"本地免密太危险"的模糊地带。
2.2 第二、三列:数据库名与用户名
这两列支持的值比较灵活:
all:匹配所有数据库或所有用户。sameuser:仅当用户名与数据库名相同时匹配。samerole:用户属于同名角色时匹配。replication:专门用于匹配流复制协议的连接,这也是为什么复制节点配置看起来像"数据库名"填了 replication 的原因。- 多个名字用逗号分隔:
app_db1,app_db2。 - 带特殊字符的名字用双引号包起来。
需要注意的是,不是所有场景都应该图省事写all all。尤其在多业务共用一个实例的情况下,一条host all all 0.0.0.0/0 scram-sha-256会把访问所有库的口子都放开,虽然密码认证仍然存在,但攻击面明显变大。我更习惯按库和账号拆开写:
host app_db app_user 10.20.30.0/24 scram-sha-256 host report_db report_user 10.20.40.0/24 scram-sha-256多写几行不费事,排错时每一行代表的业务边界却非常清楚。
2.3 第四列:客户端地址
地址列常见写法有:
- IPv4 CIDR:
192.168.1.0/24 - IPv6 CIDR:
::1/128 - 主机名:会触发反向 DNS 解析,一般不建议用,解析失败会导致匹配异常
0.0.0.0/0:匹配所有 IPv4 地址::0/0:匹配所有 IPv6 地址
这里要特别强调一个误区:0.0.0.0/0不等于所有地址。它只覆盖 IPv4,如果你的客户端走 IPv6 访问,需要单独写::0/0。很多远程连接失败的场景就是只写了 IPv4 兜底规则,结果 IPv6 客户端压根匹配不到任何条目,报出来仍然是no pg_hba.conf entry。
另外,CIDR 写法要严格按网络地址来。写192.168.1.0/24没问题,写192.168.1.99/24虽然 PostgreSQL 不会报语法错,但语义上会被规范化,容易让人误判匹配范围。
2.4 第五、六列:认证方法与选项
认证方法是整条规则的核心,决定匹配成功后用什么方式验证客户端身份。常见的有trust、reject、password、md5、scram-sha-256、peer、ident、cert,以及 LDAP、RADIUS、PAM 等外部认证。认证方法后面还可以跟选项,比如clientcert=verify-full,用于强制校验客户端证书。
下面给出一份带注释的完整示例文件片段,可以直接对着理解:
# 本地 socket 连接,使用系统用户映射到数据库用户 local all all peer # 本机 IPv4/IPv6 回环地址,使用密码认证 host all all 127.0.0.1/32 scram-sha-256 host all all ::1/128 scram-sha-256 # 办公网段,允许 app 账号访问 app_db host app_db app_user 192.168.2.0/24 scram-sha-256 # 其余所有来源默认拒绝 host all all 0.0.0.0/0 reject host all all ::0/0 reject最后两条 reject 不是可有可无的。它们让"未匹配到规则"变成"明确拒绝",至少在日志里能看出是规则主动拒绝,而不是什么诡异网络问题。
3. 认证方法怎么选:从 trust 到 scram-sha-256 的安全梯度与适用场景
3.1 常用认证方法原理对比
认证方法的选择直接影响整个数据库的安全边界。把它单独拎出来讲,是因为我见过太多人把trust当成"没空配密码时的临时方案",然后忘在生产环境里。
| 认证方法 | 是否校验密码 | 传输安全性 | 典型使用场景 |
|---|---|---|---|
| trust | 不校验 | 完全不设防 | 仅限单机本地临时调试,生产禁用 |
| reject | 直接拒绝 | 无 | 兜底规则、封锁指定来源 |
| password | 明文密码 | 密码可被抓包 | 老旧内部系统,不推荐 |
| md5 | 密码哈希 | 哈希算法相对较弱 | PG10 之前的兼容场景 |
| scram-sha-256 | 密码加盐挑战 | 强,每次挑战带盐值 | PG10+ 默认推荐 |
| peer | 映射操作系统用户 | 基于 socket 身份 | 本地运维、脚本执行 |
| cert | 客户端证书 | SSL 证书链验证 | 可信网络环境、服务间调用 |
| ident | 远程 OS 用户名映射 | 依赖 ident 服务 | 老式 Unix 环境 |
scram-sha-256是当前最推荐的口令认证方式。它把密码拆成加盐的挑战-响应过程,密码不会在网络上以明文或固定哈希形式出现,而且每次认证的盐值都不同,抓包也无法重放。从 PostgreSQL 10 开始引入,PG14 之后默认的密码加密方式就是它。
3.2 为什么本地开发也别用 trust
trust的意思是"这个来源地址过来的连接,我无条件相信,不需要任何凭证"。它不等于"免密",而是"不检查密码"。
看起来方便,实际上很危险。只要你能在服务器上执行任意命令,或者让某个进程监听本地 socket,直接psql -U postgres就能以超级用户身份进库。有人觉得"反正这是开发机",可开发机通常和测试环境、内网服务处在同一个网络里,蠕虫和横向渗透最常利用的就是这类对外开放但又没有认证的入口。
我的建议是:本地学习测试可以用,但每写完一组实验就顺手把 trust 改回 scram-sha-256。养成肌肉记忆之后,才不会在部署到线上时把同样的习惯带进去。
3.3 密码哈希版本与连接串的配合
选认证方法还要看角色密码实际存成了什么格式。password_encryption参数决定CREATE ROLE和ALTER ROLE ... PASSWORD时保存的哈希类型。PG14 默认是scram-sha-256,但如果实例是从旧版本升级来的,或者迁移时用pg_dump/pg_restore导入了历史账号,密码哈希可能还是 md5。
曾经有一个环境升级后,pg_hba.conf里已经改成了scram-sha-256,但应用侧一直报password authentication failed。查到最后发现是某个账号的密码哈希还是旧 md5 格式,客户端和服务器都必须用同一种挑战协议才能完成认证。这种情况下最简单的修复就是给对应角色重设密码:
SET password_encryption = 'scram-sha-256'; ALTER ROLE app_user WITH PASSWORD 'new_password';连接字符串那边也要留意,如果显式写了sslmode=disable,而 pg_hba 里只有hostssl规则,连接同样会失败。认证方式与连接加密方式是两层,不要混在一起判断。
4. 实际配置场景实例:本地开发、远程客户端、复制节点
4.1 本地开发的最短配置
针对一台开发机上跑 PostgreSQL、且日常只用 psql 的场景,配置其实只需要三行:
local all all peer host all all 127.0.0.1/32 scram-sha-256 host all all ::1/128 scram-sha-256第一行让操作系统用户postgres通过 socket 以 peer 方式直接访问,不用记密码。第二、三行覆盖从psql -h 127.0.0.1和psql -h ::1登录的情况,这些连接要求输入数据库密码。为什么分开?因为 peer 模式只能在 socket 连接下使用,TCP 回环连接走不了 peer;反过来,如果你把所有本地 socket 都设成 trust,等于把本机所有进程都变成潜在管理员。三行分开是最清晰也最安全的写法。
4.2 远程应用连接的最小配置
如果你的应用服务器在一个固定网段,数据库只需要对它开放。比如应用网段是10.20.30.0/24,账号是app_user,只访问app_db:
host app_db app_user 10.20.30.0/24 scram-sha-256文件尾部再放兜底拒绝规则:
host all all 0.0.0.0/0 reject host all all ::0/0 reject这里有个顺序细节很关键:兜底 reject 必须放在文件末尾。如果放在文件开头,0.0.0.0/0会匹配所有来源,上面那条具体的10.20.30.0/24永远没机会生效,应用会全部连接失败。这也是我在排查远程连接问题时最先检查的三件事之一:规则顺序、地址是否覆盖、认证方法是否匹配。
4.3 流复制节点怎么配
流复制节点的连接比较特殊,它用的是复制协议而不是普通查询协议。配置时要让复制节点的来源地址匹配到专门规则:
hostrepl replication replica_user 10.20.0.0/16 scram-sha-256hostrepl连接类型能精确区分"以复制协议建立连接"的请求,避免普通查询连接也走这条规则。如果所用的 PostgreSQL 版本不支持hostrepl,传统写法是在数据库名列填replication:
host replication replica_user 10.20.0.0/16 scram-sha-256两种写法都能实现复制连接认证,但我倾向于用hostrepl,语义更明确。卷进来的坑是:复制用户必须拥有REPLICATION属性,否则即使 pg_hba 规则匹配成功,服务器端仍会拒绝建立复制流。创建账号时记得执行:
CREATE ROLE replica_user WITH REPLICATION LOGIN PASSWORD 'replica_password';4.4 改完之后的验证三件套
每次改配置后,我建议按这个顺序验证,而不是直接关掉连接重试一遍就完事:
- 确认当前实例实际使用的 hba 文件路径:
SHOW hba_file; - 重新加载配置:
SELECT pg_reload_conf(); - 查看规则解析后的视图,确认没有语法错误:
SELECT line_number, type, database, user_name, address, auth_method, error FROM pg_hba_file_rules;pg_hba_file_rules视图非常有用,它会把你写的每一行解析成表格。如果有语法错误,对应行会出现在结果里,error列会带说明;如果加载失败,reload 时数据库也不会给提示,但视图能直接看到问题行。
验证完视图再用客户端实际连接一次:
psql "host=192.168.x.x user=app_user dbname=app_db password=xxx"能连上代表规则生效且顺序正确,连不上就看报错关键词,进入下一节的排查链路。
5. 解析三层连接失败:从客户端到 pg_hba.conf 的完整排查链路
5.1 三层过滤器
一个客户端连接最终能执行 SQL,需要依次穿过三层过滤器:
- 网络层:TCP 请求能到达 PostgreSQL 监听端口,来源 IP 被某条 pg_hba 规则匹配到。
- 认证层:规则指定的认证方法校验通过,口令或证书正确。
- 授权层:角色对目标数据库有
CONNECT权限,数据库本身存在。
很多人把这三层混在一起排查,经常折腾半天发现是网络层的 IP 网段写错了,或者卡在授权层却一直在改密码。正确第一步应该是先看报错文本到底属于哪一层。
5.2 从报错文本判断卡在哪一层
| 报错关键词 | 实际含义 | 优先检查方向 |
|---|---|---|
no pg_hba.conf entry for host | 没匹配到任何规则 | 来源 IP 是否在地址范围内、规则顺序 |
password authentication failed | 匹配到了规则,但口令不通过 | 密码是否正确、密码哈希格式是否一致 |
pg_hba.conf rejects connection | 明确匹配到了 reject 行 | 兜底规则是否放在了无关来源之前 |
database "xxx" does not exist | 授权/库层问题,认证已经通过 | 数据库名是否写错 |
permission denied for database | 通过了认证但没有 CONNECT 权限 | 角色授权 |
如果报错是no pg_hba.conf entry,说明网络层和认证层之间的匹配就断了,后面所有关于密码的检查都没有意义。很多人在这一步反复重置密码,浪费时间。
5.3 完整排查路径,按顺序执行
我排查连接问题的固定动作大概是这样:
第一步,确认实际加载的 hba 文件:
SHOW hba_file;第二步,查看规则解析结果,先排除语法错误:
SELECT * FROM pg_hba_file_rules;第三步,通过\conninfo查看当前成功连接和失败连接各自从哪个地址来:
\conninfo这一步经常发现"我以为客户端从10.0.0.5来,实际因为跳板机或者 NAT,它从10.0.0.8来",规则自然匹配不上。
第四步,手动模拟匹配过程。把客户端 IP、连接类型、用户名、数据库名代入文件从头到尾读一遍,看它会被哪条规则命中。这一步不用任何工具,纯粹靠人肉跑规则,但非常锻炼对匹配顺序的理解。
第五步,修改规则后执行 reload:
SELECT pg_reload_conf();第六步,如果还想看得更细,把连接认证日志打开。PG14 及以上可以同时打开两个参数:
log_connections = on log_auth_success = on之后每次连接尝试都会在日志里留下记录,能看到"哪条来源、哪个用户、认证成没成功"。这在判断"规则到底匹配到没有"时会非常直观。
5.4 两个我反复见过的典型误判
第一个是规则顺序问题。有人在文件最上面加了一条:
host all all 0.0.0.0/0 reject以为这是"默认拒绝所有",再往下写各种放行规则。结果由于最早匹配生效,所有 IP 全被第一条 reject 拦掉。正确做法是把 reject 放在文件末尾做兜底,放行规则放上面。
第二个是地址范围算错。客户端实际 IP 是192.168.1.100,规则写的是192.168.1.0/25,而这个网段只有192.168.1.0 - 192.168.1.127,.100虽然在范围内,但如果客户端是192.168.1.200,就会被拦在外面。排查时不要凭记忆盲写网段,让客户端执行一次ip addr或ifconfig拿到真实 IP,再用 CIDR 计算工具确认覆盖范围。
6. 配置中容易忽略的细节与我的维护习惯
6.1 先确认你改的是不是实例正在用的文件
前面提过SHOW hba_file;,这里再强调一次。系统里可能存在多个 pg_hba.conf:
- 源代码编译安装的默认数据目录下的
- 发行版安装包默认集群的
- 你自己手工 initdb 的第二实例、第三实例的
只改其中一个,其他实例完全无感。使用 systemd 管理时,也可以看一眼 service 文件里的-D参数指向哪个数据目录。多实例环境里每个实例的hba_file彼此独立,排错时必须以实例为单位看待。
6.2 IPv4 与 IPv6 都要兜底
很多默认配置里只写了 IPv4 地址,比如:
host all all 0.0.0.0/0 scram-sha-256此时 IPv6 来源连接会直接no pg_hba.conf entry。如果服务器监听了 IPv6 地址,就必须补上:
host all all ::0/0 scram-sha-256还有一种隐藏情况是 IPv4 映射 IPv6 地址,形如::ffff:192.168.1.5。这类地址在匹配时不会自动落入192.168.1.0/24的规则,很可能需要单独配置或者在应用侧强制使用 IPv4 连接。遇到连接上了但规则不匹配的诡异情况,可以用\conninfo看看实际显示的是 IPv4 还是 IPv6 地址。
6.3 reload 与 restart 别搞混
改 pg_hba.conf 只需要 reload,不需要 restart。reload 不打断现有连接,新连接会采用新规则。这个特性在处理生产问题时非常好用:你可以临时插入一条拒绝规则来封锁某个来源,reload 之后现有连接仍不断,新连接被拒,等故障处理完再删掉规则 reload 一次恢复。
restart 一般只用于修改postgresql.conf里那些需要重启才生效的参数。拿 reload 和 restart 的语义对比,就会明白为什么很多"改了 pg_hba 没生效"的疑问,最后发现是执行了 restart 而不是 reload,或者反过来。
6.4 pg_hba 通过不代表万事大吉
即使 pg_hba 匹配成功、认证通过,请求也未必能执行。它还要穿过授权层:角色有没有目标库的CONNECT权限,库内表的SELECT/INSERT权限是否足够。pg_hba.conf只管"你是谁、能不能进入数据库",不管"进去了能干什么"。
所以遇到"密码正确、连接成功、但查询报权限不足"这类问题,别再往 pg_hba 上想了,那是 GRANT 和角色管理的事。先把问题分层划清楚,能省掉大量无效排查。
6.5 我的维护习惯与收尾技巧
最后分享几个我自己长期使用的习惯。
第一,文件顶部维护一块规则地图注释,按业务模块记录常用网段。比如:
# office: 192.168.2.0/24 # staging: 10.20.30.0/24 # prod-app: 10.20.40.0/24时间一长规则多了之后,这个注释能让你快速回忆起每个网段对应什么环境,不用每次盯着地址猜。
第二,调整完配置只用 psql 里的SELECT pg_reload_conf();,比切回 shell 敲 pg_ctl 快,也避免打错数据目录。
第三,每次上线新规则前,先用pg_hba_file_rules视图确认解析结果,再发起一个测试连接。这套动作我已经重复了无数次,几乎能保证不再出现"改完配置然后半夜被叫起来"的情况。
pg_hba.conf 看起来只是一个文本文件,但它的匹配时机、顺序语义和认证方式共同构成了 PostgreSQL 的第一道防线。把逐列语法吃透,把匹配顺序刻在脑子里,再配合视图确认和日志定位,你就能在一次又一次的连接故障里保持清醒。
如果这篇文章里提到的报错你恰好正在经历,按第 5 节的排查路径一步步走,大概率能在一个 reload 周期内找到原因。