pg_hba.conf 访问控制
主机基础认证文件(pg_hba.conf, Host-Based Authentication)是 PostgreSQL 决定「谁、从哪里、以什么方式」可以连接的准入清单。它是最常被忽略、也最容易配错的安全配置——几乎所有「连不上」和「不该连的却能连」都出在这里。
前置知识
阅读本节前,建议先了解:初始化数据库集群(initdb)
基础概念
PostgreSQL 的连接准入分四步,pg_hba.conf 负责其中的第 3 步:
- 监听地址:
listen_addresses决定是否接受该网络接口的连接(详见 监听地址与端口配置) - 传输层准入:防火墙 / 安全组决定数据包能否到达
- 客户端认证:
pg_hba.conf决定该「来源 + 数据库 + 用户」组合是否放行,以及用什么方式验证身份 - 对象权限:
GRANT决定连上之后能对哪些对象做什么
pg_hba.conf 的读取规则有两条必须记牢:
- 自上而下顺序匹配,命中第一条即停止(不是「最精确匹配」,是「最先匹配」)
- 没有命中任何一行则拒绝,且报错
no pg_hba.conf entry for host ...
这两个规则决定了配置的核心原则:具体规则在前,宽泛规则在后。
| 术语 | 英文名 | 说明 |
|---|---|---|
| 主机基础认证文件 | pg_hba.conf | 客户端连接准入清单 |
| 记录 | Record | 文件中的一行,7 个字段 |
| 认证方法 | Method | trust / peer / md5 / scram-sha-256 / cert / reject |
| 连接类型 | Connection Type | local / host / hostssl / hostnossl / hostgssenc |
| 地址范围 | Address | CIDR 格式,如 192.168.1.0/24 |
| 规则视图 | pg_hba_file_rules | 系统视图,可在 SQL 中检查已加载的规则与错误 |
| 重载 | Reload | pg_ctl reload / SELECT pg_reload_conf(),不中断连接 |
语法与用法
记录格式
每行由 7 个字段组成(前 4 个必填):
text
# 连接类型 数据库 用户 地址 认证方法 [认证选项]
# TYPE DATABASE USER ADDRESS METHOD [OPTIONS]
local all postgres peer
host all all 127.0.0.1/32 scram-sha-256
host all all ::1/128 scram-sha-256
host demo app_user 10.0.0.0/8 scrub-sha-256 ← 拼写错误会被记录为错误规则
hostssl demo all 0.0.0.0/0 cert| 字段 | 取值 | 说明 |
|---|---|---|
| 连接类型 | local | Unix 域套接字连接(Linux/macOS) |
host | TCP 连接,含 SSL 与非 SSL | |
hostssl | 仅 SSL 加密的 TCP 连接 | |
hostnossl | 仅非 SSL 的 TCP 连接 | |
hostgssenc | 仅 GSSAPI 加密连接 | |
| 数据库 | all | 所有数据库 |
demo | 指定数据库 | |
demo,report | 多个库,逗号分隔 | |
replication | 物理复制连接 | |
@dblist.txt | 从文件读取库列表 | |
| 用户 | all | 所有角色 |
app_user | 指定角色 | |
app_user,report_user | 多个角色 | |
+app_group | 角色名前加 + 表示该组的所有成员 | |
@userlist.txt | 从文件读取用户列表 | |
| 地址 | 127.0.0.1/32 | IPv4 单机(CIDR 必须写掩码) |
10.0.0.0/8 | IPv4 网段 | |
::1/128 | IPv6 单机 | |
all | 任意地址(等价 0.0.0.0/0 + ::0/0) | |
samehost / samenet | 本机所有 IP / 本机所在子网 | |
| 认证方法 | trust | 无条件放行,不需密码 |
reject | 无条件拒绝 | |
peer | 用系统用户名比对数据库角色名(仅 local) | |
ident | 通过 ident 服务返回的系统用户名比对 | |
md5 | MD5 摘要口令(旧,不推荐) | |
scram-sha-256 | SCRAM-SHA-256 口令认证(推荐) | |
cert | 客户端证书认证(需 SSL) | |
ldap / radius / pam / gss / sspi | 外部认证集成 |
认证方法详解
text
# ── trust:完全放行,任何人只要有网络可达就能以任意角色连接 ──
# 危险!仅用于一次性排障,用完立刻改回
local all all trusttext
# ── reject:显式拒绝,便于在黑名单场景给出明确错误 ──
# 常用于屏蔽特定网段,比依赖「无匹配即拒绝」更易读
host all all 203.0.113.0/24 rejecttext
# ── peer:本地连接,用 OS 用户名比对数据库角色名 ──
# sudo -u postgres psql 能成功,正是因为 OS 用户 postgres == 角色 postgres
local all postgres peer
local all app_user peer
# ── ident:需 ident 服务,现代环境已少见 ──
host demo all 192.168.1.0/24 identtext
# ── scram-sha-256:生产环境的口令认证标准 ──
# 服务端与客户端双向校验,不传输明文或可重放的摘要
host all all 0.0.0.0/0 scram-sha-256text
# ── cert:双向 TLS,客户端证书的 CN 必须匹配数据库用户名 ──
# 需先在 postgresql.conf 中启用 ssl(详见 SSL 连接初始化)
hostssl demo all 10.0.0.0/8 cert clientcert=verify-fulltext
# ── ldap:企业统一认证 ──
host all all 10.0.0.0/8 ldap ldapserver=ldap.example.com ldapprefix="cn=" ldapsuffix=",ou=people,dc=example,dc=com"匹配顺序示例
text
# ── 一个完整的、顺序正确的配置示例 ──
# 1) 本地管理:只允许 postgres 系统用户以对等方式进入
local all postgres peer
# 2) 本机其他用户走口令认证
local all all scram-sha-256
# 3) 本机回环 TCP(应用在本机连接数据库)
host all all 127.0.0.1/32 scram-sha-256
host all all ::1/128 scram-sha-256
# 4) 复制连接:仅允许专用复制角色、仅内网
host replication repl_user 10.0.0.0/8 scram-sha-256
# 5) CI 迁移机:只允许 app_owner 连 demo 库,且必须走 SSL
hostssl demo app_owner 10.20.30.0/24 scram-sha-256
# 6) 应用服务器网段:只允许连业务库、只用业务账号
hostssl demo app_user 10.10.0.0/16 scram-sha-256
hostssl demo report_user 10.10.0.0/16 scram-sha-256
# 7) 屏蔽已知的扫描来源(显式拒绝,便于日志排查)
host all all 198.51.100.0/24 reject
# 8) 兜底:其余一律拒绝(这一行其实可省略,因为默认即拒绝,但显式写出更清晰)
host all all 0.0.0.0/0 reject若把第 6 步写到第 5 步之前,第 5 步就永远不会生效。顺序即优先级。
详细说明
文件位置与权限
bash
# 文件位置由初始化时的 -D 参数决定,通常为:
# Debian/Ubuntu: /etc/postgresql/18/main/pg_hba.conf
# RHEL/CentOS: /var/lib/pgsql/18/data/pg_hba.conf
# macOS(brew): /opt/homebrew/var/postgresql@18/pg_hba.conf
# 源码安装: $PGDATA/pg_hba.conf
# 用 SQL 精确问出位置(最可靠)
psql -d postgres -c "SHOW hba_file;"bash
# 权限要求:文件必须属于数据库运行用户,且不对其他用户开放写权限
sudo chown postgres:postgres /var/lib/pgsql/18/data/pg_hba.conf
sudo chmod 600 /var/lib/pgsql/18/data/pg_hba.conf
# 目录本身权限也要正确,否则 PostgreSQL 拒绝启动
sudo chmod 700 /var/lib/pgsql/18/data生效方式
bash
# 修改 pg_hba.conf 后,只需重载(reload),不会中断现有连接
sudo systemctl reload postgresql
# 或使用 SQL
psql -d postgres -c "SELECT pg_reload_conf();"
# 或使用 pg_ctl
sudo -u postgres pg_ctl -D /var/lib/pgsql/18/data reloadbash
# 注意区分:修改 postgresql.conf 中需要重启的参数(如 listen_addresses、
# shared_buffers)时必须 restart,只有 pg_hba.conf 与少数参数可 reload
sudo systemctl restart postgresql用 SQL 检查规则是否写对
pg_hba_file_rules 视图会把已加载的规则以表格形式呈现,并在 error 列给出语法问题——这是排查配置的最快方法,不必逐行肉眼比对:
sql
-- 查看已加载的全部规则
SELECT line_number, type, database, user_name, address, auth_method, error
FROM pg_hba_file_rules
ORDER BY line_number;
-- 只看有语法错误或语义问题的规则(error 非空即有问题)
SELECT line_number, error
FROM pg_hba_file_rules
WHERE error IS NOT NULL;text
# 输出示例
line_number | type | database | user_name | address | auth_method | error
-------------+-------+----------+---------------+--------------+---------------+-------
1 | local | {all} | {postgres} | | peer |
2 | local | {all} | {all} | | scram-sha-256 |
3 | host | {all} | {all} | 127.0.0.1/32 | scram-sha-256 |
4 | host | {demo} | {app_user} | 10.0.0.0/8 | scrub-sha-256 | invalid authentication method "scrub-sha-256"
(4 rows)sql
-- 修复后再次确认,并核对当前连接命中了哪条规则
SELECT line_number, type, database, user_name, address, auth_method
FROM pg_hba_file_rules
WHERE 'demo' = ANY(database) OR 'all' = ANY(database);常见认证方法的安全等级
| 方法 | 抗窃听 | 抗重放 | 是否需要密码 | 安全等级 | 建议 |
|---|---|---|---|---|---|
trust | 否 | 否 | 否 | 极低 | 仅临时排障 |
peer | 是(本地套接字) | 是 | 否 | 中 | 本地管理连接可用 |
ident | 否 | 否 | 否 | 低 | 不推荐新部署 |
md5 | 否(若未加密) | 弱 | 是 | 低 | 尽快迁移 |
scram-sha-256 | 是 | 是 | 是 | 高 | 推荐默认 |
cert | 是 | 是 | 否(凭证书) | 最高 | 高安全场景 |
reject | — | — | — | — | 黑名单 |
md5的问题不只是「摘要可被暴力破解」,更在于它的握手过程在现代威胁模型下不足以抵抗离线攻击。自 PostgreSQL 14 起,scram-sha-256已是默认的password_encryption,新集群应直接使用它。
实战示例
示例一:把「裸奔」的默认配置收紧为生产可用
默认安装后常见的一段配置是:
text
# ── 收紧前的危险配置 ──
# 危险!任意 IP 都能无密码以任意角色连接
host all all 0.0.0.0/0 trust
# 危险!明文口令上限极低
host all all ::/0 md5改造为:
text
# ── 收紧后的生产配置 ──
# TYPE DATABASE USER ADDRESS METHOD
local all postgres peer
local all all scram-sha-256
host all all 127.0.0.1/32 scram-sha-256
host all all ::1/128 scram-sha-256
hostssl demo app_user 10.10.0.0/16 scram-sha-256
hostssl demo report_user 10.10.0.0/16 scram-sha-256
host all all 0.0.0.0/0 reject
host all all ::/0 rejectbash
# 变更流程:备份 → 编辑 → 检查语法 → 重载 → 验证
cp /var/lib/pgsql/18/data/pg_hba.conf /var/lib/pgsql/18/data/pg_hba.conf.bak.$(date +%F)
sudo vi /var/lib/pgsql/18/data/pg_hba.conf
sudo -u postgres psql -d postgres -c "SELECT line_number, error FROM pg_hba_file_rules WHERE error IS NOT NULL;"
sudo systemctl reload postgresqlbash
# 验证:正确的连接应成功
psql "host=10.20.30.11 dbname=demo user=app_user sslmode=require" -c "SELECT current_user;"
# 验证:来自未授权网段应被拒绝(应看到 no pg_hba.conf entry)
psql "host=10.20.30.11 dbname=demo user=app_user hostaddr=203.0.113.5" -c "SELECT 1;"
# psql: error: connection ... FATAL: no pg_hba.conf entry for host "203.0.113.5",
# user "app_user", database "demo", no encryption示例二:用 SSL 强制约束 + 客户端证书实现免密登录
text
# 第 1 步:要求报告账号必须走 SSL,且证书 CN 必须等于数据库用户名
hostssl demo report_user 10.10.0.0/16 cert clientcert=verify-full
# 第 2 步:其余仍用口令
hostssl demo app_user 10.10.0.0/16 scram-sha-256bash
# 第 3 步:用证书连接(无需输入密码)
psql "host=db.example.com dbname=demo user=report_user \
sslmode=verify-full sslrootcert=/etc/pki/ca.crt \
sslcert=/etc/pki/report_user.crt sslkey=/etc/pki/report_user.key" \
-c "\conninfo"text
# \conninfo 输出应显示 SSL 已启用
You are connected to database "demo" as user "report_user" on host "db.example.com" (address "10.0.0.21") at port "5432".
SSL connection (protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384, compression: off)示例三:按「环境 × 角色」生成规则并做一致性校验
sql
-- 用 SQL 检查「是否存在对公网开放的 trust 规则」——最危险的一类配置
SELECT line_number, type, database, user_name, address, auth_method
FROM pg_hba_file_rules
WHERE auth_method = 'trust'
AND address IN ('0.0.0.0/0', '::/0', 'all');
-- 检查是否仍有 md5(应逐步淘汰)
SELECT line_number, user_name, address
FROM pg_hba_file_rules
WHERE auth_method IN ('md5', 'password');
-- 检查对外网暴露的口令认证(非 SSL)
SELECT line_number, database, user_name, address
FROM pg_hba_file_rules
WHERE type = 'host'
AND auth_method LIKE '%scram%'
AND address NOT IN ('127.0.0.1/32', '::1/128');bash
# 把上述检查做成上线前的门禁脚本
#!/usr/bin/env bash
set -euo pipefail
PSQL="psql -d postgres -tAc"
UNSAFE=$(${PSQL} "SELECT count(*) FROM pg_hba_file_rules
WHERE auth_method = 'trust'
AND address IN ('0.0.0.0/0','::/0','all');")
if [ "${UNSAFE}" -ne 0 ]; then
echo "拒绝上线:pg_hba.conf 存在对全网开放的 trust 规则"
exit 1
fi
ERR=$(${PSQL} "SELECT count(*) FROM pg_hba_file_rules WHERE error IS NOT NULL;")
if [ "${ERR}" -ne 0 ]; then
echo "拒绝上线:pg_hba.conf 存在加载失败的规则"
exit 1
fi
echo "pg_hba.conf 检查通过"注意事项
- 顺序即优先级,且不可回溯:第一条匹配的规则生效。宽泛规则写在前面会让后面所有精细规则变成死代码。具体→宽泛是唯一正确顺序。
- 改完必须 reload 才生效:仅保存文件不会生效。忘记 reload 是「配置明明改了却没起作用」的头号原因。
host同时匹配 SSL 与非 SSL:想让某来源必须走 SSL,必须写hostssl而不是host。反过来,hostssl规则不会拦截非 SSL 连接——那些连接会继续往下匹配,可能命中后面的host规则。0.0.0.0/0不代表「所有地址」:它只覆盖 IPv4。要覆盖全部需同时写::/0(或用all)。这是「以为封了其实没封」的常见坑。reload不踢掉现有连接:旧连接会保持原样。修改认证策略后若要立即生效,需主动断开旧连接。peer仅适用于local:把它写在host行会加载报错(可在pg_hba_file_rules.error中看到)。error列非空的规则会被跳过:一条写错的规则不会阻止服务启动,但它对应的连接会「静默地」走到更后面的规则,可能产生意外的放行。任何改动后都要检查该视图。- 不要把
pg_hba.conf交给配置管理工具随意覆盖:它与postgresql.conf常常由不同团队维护,覆盖式下发会丢掉手工加的精确规则。 - 日志是最终裁决:报错信息(
no pg_hba.conf entry for host ...)本身就说明「没有任何规则命中」,这通常不是密码问题,而是地址/库名/用户名写错了。
bash
# 打开认证相关的详细日志(postgresql.conf),便于定位
# log_connections = on
# log_disconnections = on
# log_line_prefix = '%m [%p] %q%u@%d %r '
sudo systemctl reload postgresql
sudo tail -f /var/log/postgresql/postgresql-18-main.log- 阶段十六会深入认证机制:本节只覆盖「怎么写、怎么生效」的工程实践。认证协议细节(SCRAM 握手、GSSAPI、LDAP 集成、行级安全的配合)会在 16.2 客户端认证中展开,届时可结合本节配置对照理解。
最佳实践
- 默认拒绝,白名单放行:不写兜底
0.0.0.0/0 reject也能拒绝,但显式写出能让意图可读、便于 review。 - 一行一意图,并写注释:每个规则的用途、责任团队、变更日期写清。
pg_hba.conf是少数需要人肉 review 的配置。 - 生产禁用
trust与md5:新集群直接用scram-sha-256;存量集群制定迁移计划,先ALTER ROLE重设密码(使其以 SCRAM 存储),再切换方法。 - 本地管理用
peer,远程管理用scram-sha-256+hostssl:管理连接要给最窄的地址范围。 - 优先
hostssl而非host:让「加密」成为默认而非可选项。配合sslmode=require可有效防止降级。 - 配置纳入版本控制:
pg_hba.conf用 Git 管理(注意不要提交含敏感地址的注释),每次变更留 commit 记录,便于回滚与审计。 - 变更走「备份 → 检查 → 重载 → 验证」四步:永远先在测试环境演练,尤其是收紧类变更,否则可能把自己也挡在外面。
- 给自己留后路:变更前确认还有一个可用的本地
peer通道(如sudo -u postgres psql),避免改错后完全无法进入。
bash
# 变更前的自保检查:确认本地 peer 通道可用
sudo -u postgres psql -d postgres -c "SELECT 'peer ok';"
# 变更后立刻复验该通道(若失败,恢复备份并 reload)
sudo -u postgres psql -d postgres -c "SELECT 'peer still ok';"下一节
继续学习:监听地址与端口配置