# root certificate file does not exist：PostgreSQL verify-full

> libpq 用 sslmode=verify-full 时，缺少 root.crt、CA 不对或服务器证书与主机名不符都会报错。提供正确的 CA，不要把 sslmode 降级。

Source: https://ssl.sh/zh/errors/postgres-sslmode-verify-full-error/
Last updated: 2026-11-10

**这个报错的意思是：你要求 libpq 验证服务器（sslmode 为 `verify-ca` 或 `verify-full`），却没给它用来验证的 CA 文件。** 同类报错 `certificate verify failed`、`server certificate for "..." does not match host name "..."`，分别表示 CA 文件不对，或者证书不包含你用的主机名。

## 快速修复

修法是把签发服务器证书的 CA 交给 libpq，用 `sslrootcert` 指定文件路径：

```bash
psql "host=db.example.com dbname=app user=app sslmode=verify-full sslrootcert=/etc/ssl/certs/db-ca.pem"
```

- **云数据库：** 从云厂商文档或控制台下载 CA 证书。
- **自建服务器 + [私有 CA](https://ssl.sh/zh/cookbook/private-ca/)：** 用这个 CA 的根证书。
- **服务器用公网受信任证书（客户端 PostgreSQL 16+）：** `sslrootcert=system` 使用操作系统信任库。

也可以把文件放到默认位置：`~/.postgresql/root.crt`（Windows 是 `%APPDATA%\postgresql\root.crt`）。

## 原因

libpq 验证失败有 5 种原因：没有 CA 文件、CA 不匹配、主机名不匹配、证书过期、服务器证书配置错误。前三种能从报错文字直接区分。

### 1. 没有 CA 文件

- **确认：** 就是上面这条报错，里面写着 libpq 检查的路径。
- **修复：** 提供 `sslrootcert`，或者把 CA 放到运行客户端那个用户的默认路径下。

### 2. CA 文件和服务器不匹配

- **确认：** 报 `SSL error: certificate verify failed`。查看服务器证书由谁签发：

```bash
openssl s_client -starttls postgres -connect db.example.com:5432 -servername db.example.com </dev/null 2>/dev/null | openssl x509 -noout -subject -issuer -enddate
```

- **修复：** 使用该签发者的根证书。服务器证书是[自签名](https://ssl.sh/zh/glossary/self-signed-certificate/)的（subject 和 issuer 相同）时，这张证书本身就是 CA 文件。服务器没发[中间证书](https://ssl.sh/zh/glossary/intermediate-certificate/)时，把中间证书加到服务器的 `server.crt` 里，或者加进客户端 CA 文件。

### 3. 主机名不匹配（verify-full）

- **确认：** 报 `server certificate for "db-primary.internal" does not match host name "203.0.113.10"`。
- **修复：** 用证书 [SAN](https://ssl.sh/zh/glossary/san/) 里有的名字连接，或者重新签发包含所有客户端会用到的名字和 IP 的服务器证书。用 IP 连接时，IP 必须以 IP SAN 的形式出现在证书里。云数据库要用控制台给出的连接地址（域名），不要直接用解析出来的 IP。

### 4. 服务器证书过期

- **修复：** 更新 `server.crt`，执行 `SELECT pg_reload_conf();`（重载时 PostgreSQL 会重新读取 SSL 文件）。

### 5. 服务器证书文件配置错误

- **确认：** 服务器日志显示 `could not load server certificate file`，或者 SSL 没开启。
- **修复：** postgresql.conf 里设置 `ssl = on`、`ssl_cert_file`、`ssl_key_file`；私钥要属于 postgres 用户、权限 600（或按文档要求属于 root 并给组读权限）。

## 各环境修法

各驱动的规则和 libpq 一致：sslmode 设为 verify-full，再用 sslrootcert 或驱动的 ca 参数指定 CA 文件。

**JDBC（pgJDBC）：** `jdbc:postgresql://db.example.com/app?sslmode=verify-full&sslrootcert=/path/db-ca.pem`。

**Python（psycopg）：** 传 `sslmode="verify-full", sslrootcert="/path/db-ca.pem"`，规则和 libpq 一样。

**Node.js（pg）：** `ssl: { ca: fs.readFileSync("/path/db-ca.pem").toString(), rejectUnauthorized: true }`；除非覆盖 `checkServerIdentity`，驱动会检查主机名。

**Docker / Kubernetes：** 把 CA 作为 Secret 或 ConfigMap 挂载，设置环境变量 `PGSSLROOTCERT` 和 `PGSSLMODE=verify-full`。

**查看连接实际用了什么：** 在 `psql` 里执行 `\conninfo`，会显示是否使用 SSL 以及协议和加密套件；服务器端执行 `SELECT * FROM pg_stat_ssl;` 能看到哪些会话在用 TLS。

**云数据库只读实例、代理地址：** 只读实例、数据库代理的连接地址和主实例不同，确认每个地址都在证书覆盖范围内，或者按云厂商文档使用对应的 CA 和地址。

**连接池（PgBouncer）：** 客户端到连接池、连接池到数据库是两段独立的 TLS（`client_tls_*` 和 `server_tls_*`），两段都要验证。

## 不要这样做

降级 sslmode 或关闭校验能消除报错，代价是客户端再也认不出冒充的数据库服务器。

- **为了消除报错把 sslmode 降到 `require` 或 `prefer`。** 加密还在，但失去了服务器身份验证，冒充的服务器可以截获账号密码。
- **在 Node.js 里设 `rejectUnauthorized: false`，或其他驱动里的等价设置。** 结果一样。
- **为了躲开主机名不匹配，改用 IP 加 verify-ca。** 应该修正证书里的名字。
