PostgreSQL 在 VPS 上连接不上时,很多人第一反应是“端口没开”或者“密码错了”。但 PostgreSQL 的连接链路比 MySQL 更容易被几层配置同时影响:服务有没有启动、5432 是否监听、监听在哪个地址、pg_hba.conf 有没有匹配到客户端 IP、认证方式是否一致、防火墙和安全组有没有放行。
这篇文章按排障顺序来,不建议一开始就把 listen_addresses='*'、0.0.0.0/0 全部放开。数据库是高价值目标,排查连接问题时也要保持最小暴露原则。
PostgreSQL 连接问题大致分四类:
| 报错现象 | 更可能的问题 |
|---|---|
Connection refused | 服务没启动、端口没监听、监听地址不对 |
No route to host / 超时 | 防火墙、安全组、网络路径拦截 |
no pg_hba.conf entry | pg_hba.conf 没有匹配规则 |
password authentication failed | 密码、用户、认证方式或数据库权限问题 |
先不要改配置。先在 VPS 本机执行:
sudo systemctl status postgresql --no-pager
sudo pg_isready
如果本机都不 ready,先排服务启动;如果本机正常、远程不通,再排监听地址、防火墙和 pg_hba.conf。
Ubuntu / Debian 包安装的 PostgreSQL 通常由 systemd 管理:
sudo systemctl status postgresql --no-pager
看最近日志:
sudo journalctl -u postgresql -n 100 --no-pager
不同版本也可以看具体实例:
pg_lsclusters
输出里重点看:
Status是否 online;Port是否为 5432 或你自定义的端口;Data directory是否正确;- 日志文件路径在哪里。
如果服务启动失败,常见原因包括:
- 数据目录权限不对;
- 端口已被另一个 PostgreSQL 实例占用;
- 磁盘满或 inode 耗尽;
- 配置文件语法错误;
- 共享内存、连接数、内核参数设置过大。
PostgreSQL 官方文档也明确提到,启动失败要先看日志;例如端口被占用时会出现类似 Address already in use,连接失败时客户端会看到 Connection refused。
服务显示 running 不代表远程一定能连。继续看监听端口:
sudo ss -lntup | grep 5432
常见输出有两种:
127.0.0.1:5432
0.0.0.0:5432
如果只监听 127.0.0.1:5432,说明 PostgreSQL 只接受本机连接。远程客户端访问 VPS 公网 IP 时,自然会失败。
也可以本机测试:
psql -h 127.0.0.1 -U postgres -d postgres
如果本机连接正常,问题就更可能在远程监听、防火墙、安全组或 pg_hba.conf。
PostgreSQL 默认通常只监听本机。配置文件位置可以这样找:
sudo -u postgres psql -c "SHOW config_file;"
编辑配置前先备份:
sudo cp /etc/postgresql/*/main/postgresql.conf /etc/postgresql/postgresql.conf.bak.$(date +%F-%H%M) 2>/dev/null || true
查看当前值:
sudo -u postgres psql -c "SHOW listen_addresses;"
如果确实需要远程访问,更建议绑定到内网地址或指定地址,而不是直接写 *。例如:
listen_addresses = '127.0.0.1,10.0.0.5'
如果你的 VPS 没有内网,只能临时公网访问,也要配合严格的 pg_hba.conf 和安全组白名单,不要把数据库开放给全网。
修改后重启:
sudo systemctl restart postgresql
再检查:
sudo ss -lntup | grep 5432
listen_addresses 决定 PostgreSQL 是否监听某个地址,pg_hba.conf 决定哪些客户端可以通过什么方式认证。
找到文件位置:
sudo -u postgres psql -c "SHOW hba_file;"
编辑前备份:
sudo cp /etc/postgresql/*/main/pg_hba.conf /etc/postgresql/pg_hba.conf.bak.$(date +%F-%H%M) 2>/dev/null || true
一个更安全的远程访问规则应该限定来源 IP,例如只允许你的办公公网 IP:
host mydb myuser 203.0.113.10/32 scram-sha-256
不建议这样写:
host all all 0.0.0.0/0 md5
它不仅范围过大,还可能使用较旧认证方式。PostgreSQL 文档里也强调,pg_hba.conf 是按顺序匹配的,第一条匹配规则生效;如果没有规则匹配,就会拒绝连接。
修改后可以 reload:
sudo systemctl reload postgresql
或者:
sudo -u postgres psql -c "SELECT pg_reload_conf();"
如果怀疑规则有语法问题,可以查:
sudo -u postgres psql -c "SELECT * FROM pg_hba_file_rules WHERE error IS NOT NULL;"
远程连接不是只要端口通就行,还需要用户有权限连接对应数据库。
进入 PostgreSQL:
sudo -u postgres psql
查看角色:
\du
创建业务用户示例:
CREATE USER appuser WITH PASSWORD '替换为强密码';
CREATE DATABASE appdb OWNER appuser;
GRANT CONNECT ON DATABASE appdb TO appuser;
测试连接:
psql -h 127.0.0.1 -U appuser -d appdb
如果本机能连,远程不能连,就继续看防火墙和安全组。
本机 UFW:
sudo ufw status verbose
如果只允许指定 IP:
sudo ufw allow from 203.0.113.10 to any port 5432 proto tcp
不要直接:
sudo ufw allow 5432/tcp
除非你非常确定云安全组已经限制来源 IP。
还要检查云厂商控制台里的安全组或防火墙。很多 VPS 用户只改了系统内的 UFW,却忘了控制台安全组仍然拦截 5432。
从客户端测试端口:
nc -vz 你的VPS公网IP 5432
如果端口不通,优先排网络层;如果端口通但认证失败,再回到 PostgreSQL 配置层。
如果 PostgreSQL 跑在 Docker 里,还要看容器端口是否映射到宿主机:
docker ps
你需要看到类似:
0.0.0.0:5432->5432/tcp
如果只有容器内部 5432/tcp,但没有宿主机映射,外部访问不到。
Docker Compose 示例:
services:
postgres:
image: postgres:16
ports:
- "127.0.0.1:5432:5432"
environment:
POSTGRES_DB: appdb
POSTGRES_USER: appuser
POSTGRES_PASSWORD: "替换为强密码"
生产环境更推荐绑定 127.0.0.1,让应用同机访问,或者通过内网/VPN/堡垒机访问,而不是直接把数据库暴露到公网。
建议按这个顺序走:
systemctl status postgresql看服务;journalctl -u postgresql -n 100看启动错误;pg_lsclusters看版本、端口、数据目录;ss -lntup | grep 5432看监听地址;SHOW listen_addresses;看是否允许远程监听;SHOW hba_file;检查pg_hba.conf;- 用
pg_hba_file_rules检查规则错误; - 检查用户、数据库和
CONNECT权限; - 检查 UFW 和云安全组;
- 最后再看 Docker 端口映射或应用连接串。
这样排查的好处是每一步都能缩小范围,避免把监听、认证、防火墙、密码问题混在一起。
即使你已经修好连接问题,也不代表应该长期暴露 5432。更安全的做法是:
- 应用和数据库在同一台 VPS:监听
127.0.0.1; - 应用和数据库在同一云商内网:只监听内网 IP;
- 远程管理:用 SSH 隧道;
- 多台服务器访问:用 WireGuard / Tailscale / ZeroTier;
- 临时排障:安全组只放行你的固定 IP,用完关闭。
可以参考这篇数据库安全文章:
VPS 上 PostgreSQL 连接不上,最容易混淆的是三件事:端口是否监听、pg_hba.conf 是否允许、网络层是否放行。Connection refused 往往是服务或监听问题;no pg_hba.conf entry 是认证规则问题;连接超时更像防火墙、安全组或线路问题。
排查时先从本机开始,再逐层走到远程访问。不要一上来就把 listen_addresses='*' 和 0.0.0.0/0 放开。数据库连通性问题可以修,但数据库暴露公网带来的风险,往往比连接失败本身更麻烦。
