必须显式设置sslmode=verify-full并指定sslrootcert绝对路径,服务端还需启用ssl=on及正确配置ssl_cert_file/ssl_key_file,否则TLS握手失败。

PostgreSQL连接字符串必须显式启用sslmode=verify-full
默认的 sslmode=disable 会跳过所有 TLS 验证,根本不会加载证书;sslmode=require 虽启用加密,但不校验证书链,无法配合自签名证书使用。真正起效的是 sslmode=verify-full —— 它强制验证服务器证书的 CN/Subject、签名链和主机名匹配。
此时 GORM 才会读取你通过环境变量或连接参数指定的证书文件。常见错误是只改了 sslmode 却没配证书路径,结果报错:pq: server does not support SSL(实际是客户端拒绝接受未验证的握手)。
证书路径要通过 sslrootcert 参数传入绝对路径
GORM 不识别 CERT_PATH 这类环境变量,也不自动查找 ~/.postgresql/root.crt。你必须把证书路径拼进 DSN:
host=mydb.example.com user=app password=secret dbname=prod port=5432 sslmode=verify-full sslrootcert=/etc/ssl/certs/my-ca.crt
注意三点:
-
sslrootcert值必须是服务端可读的绝对路径,相对路径(如./ca.crt)在容器或 systemd 下大概率失败 - 证书文件需为 PEM 格式,不能是 DER 或带密码的 PKCS#12
- 若证书链包含中间 CA,必须合并到同一文件中,顺序为:leaf → intermediate → root
Go 程序启动前要确保证书文件存在且权限正确
PostgreSQL 驱动(pgconn)在首次连接时才读取 sslrootcert 文件。如果路径不存在或权限不足(比如被 umask 拦截),错误信息非常隐晦:failed to connect to `host=mydb.example.com user=app database=prod`: server closed the connection unexpectedly。
建议在初始化数据库前加一次预检:
if _, err := os.Stat("/etc/ssl/certs/my-ca.crt"); os.IsNotExist(err) {
log.Fatal("missing SSL root cert:", err)
}
同时确认运行用户(如 www-data 或容器内非 root 用户)对该路径有读权限 —— 这比证书内容错误更常导致连接失败。
用 pgx 替代原生驱动可绕过部分 GORM 的 SSL 限制
GORM v1.24+ 默认用 pgconn,但对自签名证书的调试支持弱。如果你需要查看详细握手日志(比如证书不匹配的具体字段),可临时切换到底层 pgx 驱动:
import "github.com/jackc/pgx/v5/pgxpool"
config, _ := pgxpool.ParseConfig(dsn)
config.ConnConfig.TLSConfig = &tls.Config{
RootCAs: rootCAPool,
InsecureSkipVerify: false, // 关键:保持验证
}
db, _ := gorm.Open(postgres.New(postgres.Config{
Conn: config.Acquire(context.Background()),
})), &gorm.Config{})
这样你能直接控制 TLSConfig,比如设置 VerifyPeerCertificate 回调做自定义校验逻辑——不过绝大多数场景,老老实实用 sslrootcert + verify-full 就够了。
真正容易被忽略的是:PostgreSQL 服务端必须配置 ssl_cert_file 和 ssl_key_file 指向有效的自签名证书对,且 ssl = on 已启用;否则客户端再怎么配,连 TLS 握手第一步都走不完。


















