监听地址与端口配置
监听地址(Listen Address)与端口决定 PostgreSQL 在哪些网络接口上「开门迎客」。它是最基础的一道边界:门开得太大,pg_hba.conf 再严也只是第二道防线;门开得太小,应用又连不上。
前置知识
阅读本节前,建议先了解:pg_hba.conf 访问控制
基础概念
PostgreSQL 服务进程默认只监听本机回环地址,这是「安全优先」的设计取舍。要让其他机器连上,必须同时满足三个条件:
| 层次 | 配置项 | 作用 |
|---|---|---|
| 监听层 | listen_addresses | 服务在哪些网卡上接受 TCP 连接 |
| 监听层 | port | 监听的 TCP 端口,默认 5432 |
| 准入层 | pg_hba.conf | 该来源 + 库 + 用户是否放行 |
| 网络层 | 防火墙 / 安全组 | 数据包能否到达该端口 |
只改 listen_addresses 而不改 pg_hba.conf,连接仍会被拒绝(报 no pg_hba.conf entry);反之只改 pg_hba.conf 而 listen_addresses 仍是 localhost,外部连 TCP 时会被直接拒绝(Connection refused)。两者必须同时正确。
listen_addresses 的取值语义:
| 取值 | 含义 | 典型场景 |
|---|---|---|
localhost | 仅回环(127.0.0.1 与 ::1),默认值 | 单机应用,仅本机连接 |
* | 监听所有可用 IPv4 与 IPv6 接口 | 需要外部访问,靠 pg_hba.conf 与防火墙收窄 |
0.0.0.0 | 仅所有 IPv4 接口 | 明确只支持 IPv4 的环境 |
:: | 仅所有 IPv6 接口 | 纯 IPv6 环境 |
10.0.0.5,127.0.0.1 | 指定多个地址,逗号分隔 | 推荐:只监听内网网卡 + 回环 |
空字符串 '' | 完全不监听 TCP,仅 Unix 套接字 | 极高安全要求、仅本机连接 |
推荐做法是显式列出内网地址,而不是用
*。这样即使防火墙配错,公网网卡上也没有服务在监听。
| 术语 | 英文名 | 说明 |
|---|---|---|
| 监听地址 | Listen Address | postgresql.conf 中的 listen_addresses 参数 |
| 回环地址 | Loopback Address | 127.0.0.1 / ::1,仅本机可达 |
| 通配监听 | Wildcard Listen | *,监听所有接口 |
| Unix 域套接字 | Unix Domain Socket | 本机进程间通信通道,路径由 unix_socket_directories 指定 |
| 连接槽 | Connection Slot | 每个连接占用一个服务端进程 |
| 最大连接数 | max_connections | 全局连接上限,默认 100 |
| 超级用户保留连接 | superuser_reserved_connections | 为超级用户预留的连接槽 |
语法与用法
postgresql.conf 相关参数
ini
# /var/lib/pgsql/18/data/postgresql.conf
# ── 监听地址 ──
# 仅本机(默认,最安全)
listen_addresses = 'localhost'
# 监听所有接口(需靠 pg_hba.conf 与防火墙收窄,慎用)
# listen_addresses = '*'
# 不推荐
# 推荐:只监听内网网卡 + 回环
# listen_addresses = '10.10.1.5,127.0.0.1'
# ── 端口 ──
port = 5432
# ── Unix 域套接字目录(本机连接,绕过 TCP,性能更好) ──
unix_socket_directories = '/var/run/postgresql,/tmp'
# ── 连接规模 ──
max_connections = 200
superuser_reserved_connections = 3ini
# ── 与连接体验相关的参数 ──
# 客户端认证超时:防止空闲连接占用槽位,默认 60 秒
authentication_timeout = 30
# TCP keepalive:及时发现被网络中断的「僵尸连接」
tcp_keepalives_idle = 120
tcp_keepalives_interval = 30
tcp_keepalives_count = 5
# 空闲事务超时(在 postgresql.conf 中设置默认值)
idle_in_transaction_session_timeout = '10min'
idle_session_timeout = '30min'重要:
listen_addresses与port属于需要重启的参数,reload不生效。
bash
# 修改后必须重启
sudo systemctl restart postgresql
# 确认服务真的监听成功
sudo ss -lntp | grep 5432查看当前生效值
sql
-- 查看监听配置
SHOW listen_addresses;
SHOW port;
SHOW unix_socket_directories;
SHOW max_connections;
-- 从 pg_settings 中看更完整的元信息(含是否需重启、来源文件)
SELECT name, setting, unit, boot_val, source, pending_restart
FROM pg_settings
WHERE name IN ('listen_addresses', 'port', 'max_connections',
'unix_socket_directories', 'superuser_reserved_connections')
ORDER BY name;text
# 输出示例
name | setting | unit | source | pending_restart
-----------------------------+-----------------+------+----------+-----------------
listen_addresses | localhost | | default | f
max_connections | 200 | | file | f
port | 5432 | | file | f
superuser_reserved_connections | 3 | | default | f
unix_socket_directories | /var/run/postgresql,/tmp | | file | f查看实际连接来源
sql
-- 查看当前所有连接:从哪来、用什么方式认证
SELECT pid,
usename,
application_name,
client_addr,
client_port,
ssl,
state,
backend_start
FROM pg_stat_activity
WHERE backend_type = 'client backend'
ORDER BY backend_start;
-- 按客户端地址聚合,快速看出有哪些机器在连
SELECT coalesce(client_addr::text, 'local socket') AS client,
count(*) AS conns,
string_agg(DISTINCT usename, ', ') AS users
FROM pg_stat_activity
WHERE backend_type = 'client backend'
GROUP BY 1
ORDER BY conns DESC;详细说明
Unix 域套接字 vs TCP
本机连接有两条路径,优先使用 Unix 域套接字:
| 维度 | Unix 域套接字 | TCP(回环) |
|---|---|---|
| 连接字符串 | 省略 host,或用 host=/var/run/postgresql | host=127.0.0.1 |
| 路径参数 | unix_socket_directories | — |
| 性能 | 略高(无网络栈开销) | 略低 |
| 认证方式 | 支持 peer 与 ident | 只支持口令 / 证书类 |
pg_hba.conf 类型 | local | host / hostssl |
| 可被网络嗅探 | 否 | 明文时是(sslmode=disable) |
| 适合 | 单机应用、管理脚本 | 容器、跨主机 |
bash
# 使用 Unix 域套接字(默认行为)
psql -h /var/run/postgresql -U app_user -d demo
# 或在连接串中显式指定
psql "host=/var/run/postgresql dbname=demo user=app_user"
# 使用 TCP 回环
psql "host=127.0.0.1 port=5432 dbname=demo user=app_user"sql
-- 在服务端区分连接来源
SELECT pid, usename,
CASE WHEN client_addr IS NULL THEN 'unix socket' ELSE client_addr::text END AS via
FROM pg_stat_activity
WHERE backend_type = 'client backend';端口冲突与多实例
bash
# 检查端口占用(Linux)
sudo ss -lntp | grep -E ':5432|:5433'
sudo lsof -iTCP -sTCP:LISTEN -P -n | grep postgres
# macOS
sudo lsof -nP -iTCP:5432 -sTCP:LISTENbash
# 多实例共存:为每个集群分配不同端口与 socket 目录
# 实例 A(主库,默认端口)
# postgresql.conf: port = 5432
# postgresql.conf: unix_socket_directories = '/var/run/postgresql'
# 实例 B(测试库)
# postgresql.conf: port = 5433
# postgresql.conf: unix_socket_directories = '/var/run/postgresql-18b'
# 用 pg_ctl 分别启动
sudo -u postgres pg_ctl -D /var/lib/pgsql/18/data -o "-p 5432" start
sudo -u postgres pg_ctl -D /var/lib/pgsql/18b/data -o "-p 5433" startbash
# 连接非默认端口
psql -h 127.0.0.1 -p 5433 -U app_user -d demo
# 或使用环境变量
PGPORT=5433 psql -h 127.0.0.1 -U app_user -d demo连接数规划
连接数不是「越大越好」:每个连接是一个操作系统进程,占用内存(work_mem 等按连接分配)与调度开销。
| 参数 | 默认值 | 说明 |
|---|---|---|
max_connections | 100 | 全局上限,修改需重启 |
superuser_reserved_connections | 3 | 为超级用户预留,普通角色无法占用 |
reserved_connections | 0 | 预留槽位,配合 pg_use_reserved_connections 使用 |
rolconnlimit | -1 | 单角色上限(在角色上设置,见 postgres-user) |
sql
-- 检查连接预算:理论可用槽位 = max_connections - superuser_reserved_connections
SELECT current_setting('max_connections')::int AS max_conn,
current_setting('superuser_reserved_connections')::int AS reserved,
current_setting('max_connections')::int
- current_setting('superuser_reserved_connections')::int AS usable;
-- 连接占用率(生产建议持续监控,超过 80% 就该告警)
SELECT count(*) AS used,
current_setting('max_connections')::int AS max_conn,
round(100.0 * count(*) / current_setting('max_connections')::int, 1) AS pct
FROM pg_stat_activity;
-- 按状态细分,识别连接是否需要连接池
SELECT state, count(*)
FROM pg_stat_activity
GROUP BY state
ORDER BY count(*) DESC;text
# 输出示例
state | count
---------------------+-------
idle | 142
active | 9
idle in transaction | 3
(3 rows)
idle数量远大于active,通常意味着应用侧缺少连接池,而不是数据库需要更多连接。
实战示例
示例一:从「仅本机」改造成「内网可访问」
目标:让 10.10.0.0/16 网段的应用服务器可以连接本机的 demo 库。
bash
# 第 0 步:确认本机内网地址(不要凭记忆写)
ip -4 addr show | grep -w inet
# inet 10.10.1.5/24 brd 10.10.1.255 scope global eth0
# macOS
ipconfig getifaddr en0ini
# 第 1 步:编辑 postgresql.conf(改这两项都要重启)
# listen_addresses = '10.10.1.5,127.0.0.1'
# port = 5432text
# 第 2 步:编辑 pg_hba.conf(reload 即可生效)
# TYPE DATABASE USER ADDRESS METHOD
hostssl demo app_user 10.10.0.0/16 scram-sha-256
host all all 10.10.0.0/16 rejectbash
# 第 3 步:防火墙放行(只对本网段开放)
sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="10.10.0.0/16" port port="5432" protocol="tcp" accept'
sudo firewall-cmd --reload
# Ubuntu / ufw
sudo ufw allow from 10.10.0.0/16 to any port 5432 proto tcp
# 第 4 步:重启并验证监听
sudo systemctl restart postgresql
sudo ss -lntp | grep 5432
# LISTEN 0 244 10.10.1.5:5432 0.0.0.0:*
# LISTEN 0 244 127.0.0.1:5432 0.0.0.0:*bash
# 第 5 步:从应用服务器验证连通性(顺序:端口 → 认证 → 权限)
# 5.1 端口是否可达
nc -zv 10.10.1.5 5432
# 5.2 认证是否通过
psql "host=10.10.1.5 dbname=demo user=app_user sslmode=require" -c "SELECT current_user, inet_server_addr();"
# 5.3 权限是否足够
psql "host=10.10.1.5 dbname=demo user=app_user sslmode=require" -c "\dt"示例二:排查「外部连不上」
按顺序逐层排除,不要跳步:
bash
# 第 1 层:服务是否在监听预期地址
sudo ss -lntp | grep 5432
# 若只有 127.0.0.1 → listen_addresses 未包含内网地址(需改配置并重启)
# 第 2 层:本机能否自连该地址(排除防火墙)
psql "host=10.10.1.5 dbname=demo user=app_user" -c "SELECT 1;"
# 若失败而 127.0.0.1 成功 → 防火墙或路由问题
# 第 3 层:对端到本机的可达性
nc -zv 10.10.1.5 5432
# 若 connection refused → 端口未监听或被防火墙 DROP
# 若 timeout → 安全组 / 网络 ACL 拦截
# 第 4 层:数据库日志
sudo tail -n 50 /var/log/postgresql/postgresql-18-main.logtext
# 常见报错与对应层次
# Connection refused
# → 端口没监听:listen_addresses 不含该地址,或服务未启动,或防火墙 REJECT
# FATAL: no pg_hba.conf entry for host "10.10.5.20", user "app_user", database "demo"
# → 监听已通,pg_hba.conf 未放行该来源
# FATAL: password authentication failed for user "app_user"
# → pg_hba 已匹配,密码或 SCRAM 存储有问题
# FATAL: remaining connection slots are reserved for ...
# → 连接数耗尽sql
-- 在数据库内确认「我看到的客户端地址」是什么(NAT 环境下常与预期不同)
SELECT inet_client_addr() AS client_ip,
inet_server_addr() AS server_ip,
inet_server_port() AS server_port;
-- 查看是否有连接数耗尽的风险
SELECT count(*) AS total,
count(*) FILTER (WHERE state = 'idle') AS idle_cnt,
count(*) FILTER (WHERE state = 'active') AS active_cnt
FROM pg_stat_activity;示例三:用脚本固化一份监听与端口基线检查
bash
#!/usr/bin/env bash
# check-listen.sh:上线前的监听与端口基线检查
set -euo pipefail
PSQL="psql -d postgres -tAc"
echo "── 1) 监听地址应为内网地址 + 回环 ──"
LISTEN=$(${PSQL} "SHOW listen_addresses;")
echo "listen_addresses = ${LISTEN}"
if [ "${LISTEN}" = "*" ]; then
echo "警告:监听所有接口,请确认防火墙与 pg_hba.conf 已收窄"
fi
echo "── 2) 端口应为非默认或显式登记 ──"
${PSQL} "SHOW port;"
echo "── 3) 连接预算检查 ──"
${PSQL} "SELECT count(*) || ' / ' || current_setting('max_connections')
AS conn_usage FROM pg_stat_activity;"
echo "── 4) 是否存在非预期的远程连接来源 ──"
${PSQL} "SELECT coalesce(client_addr::text,'unix socket') || ' x ' || count(*)
FROM pg_stat_activity
WHERE backend_type = 'client backend'
GROUP BY 1 ORDER BY 1;"
echo "检查完成"bash
chmod +x check-listen.sh
./check-listen.sh注意事项
listen_addresses与port需要重启:SELECT pg_reload_conf()对这两项无效。改完必须systemctl restart postgresql,并用ss -lntp确认监听结果。*与0.0.0.0都是「全开」:0.0.0.0只覆盖 IPv4,::只覆盖 IPv6,只有*同时覆盖两者。在只有 IPv4 的机器上三者常用效果相同,但语义不同,别混用。- 云环境要看安全组:在 AWS / 阿里云等环境,本机
iptables往往不是最终裁决者,安全组与 VPC 网络 ACL 才是。排查时不要只在机器上查。 - 容器里的
localhost是容器自己:Docker 容器内的listen_addresses = 'localhost'只监听容器回环,宿主机无法访问。容器内应设为*,再用-p 127.0.0.1:5432:5432限制暴露范围。 listen_addresses不改变pg_hba.conf的必要性:即使只监听内网,pg_hba.conf仍需正确配置;反过来,pg_hba.conf再严也无法阻止服务在公网网卡上监听。- NAT / 负载均衡下地址会变:经过 NAT 或代理后,
inet_client_addr()返回的是代理地址而非真实客户端 IP。这会导致pg_hba.conf的地址匹配失效,需要改用代理后的统一网段。 - 改端口后别忘更新所有下游:应用连接串、监控采集、备份脚本、
pg_hba.conf的复制规则、容器编排的端口映射。 max_connections调大要先算内存:每个连接至少占用数 MB(含work_mem、维护内存、栈等)。盲目从 100 调到 1000 可能直接触发 OOM,正确做法是加连接池。
sql
-- 估算:每个连接的基础内存开销(粗略)
SELECT name, setting, unit
FROM pg_settings
WHERE name IN ('work_mem', 'maintenance_work_mem', 'shared_buffers',
'temp_buffers', 'max_connections');最佳实践
- 显式指定地址,不用
*:listen_addresses = '10.10.1.5,127.0.0.1'既满足内网访问,又天然阻断了公网。若必须用*,请确保防火墙与之同步收窄。 - 保留回环监听:始终把
127.0.0.1写进列表,保证本机监控、备份、迁移工具能连上。 - 本机应用走 Unix 域套接字:省去加密开销,且可用
peer认证,避免在磁盘上留任何口令。 - 用连接池而不是加连接数:应用连接数超过
max_connections的一半时,先上 PgBouncer(事务级池化)而非调大参数。 - 为连接设超时:
idle_in_transaction_session_timeout与idle_session_timeout能自动清理「忘记提交的事务」,避免长事务阻塞 VACUUM。 - 把监听配置纳入巡检:定期核对
ss -lntp实际监听与配置是否一致,及时发现被手工放开的接口。 - 变更走完整链路检查:改完监听参数后,按「服务端监听 → 防火墙 → 客户端连通 → 认证 → 权限」五步逐层验证,而不是只在本机试一下。
bash
# 一键完成五步验证的模板
HOST=10.10.1.5; PORT=5432; DB=demo; USER=app_user
set -e
sudo ss -lntp | grep ":${PORT}" # 1 监听
nc -zv "${HOST}" "${PORT}" # 2 网络
psql "host=${HOST} port=${PORT} dbname=${DB} user=${USER} sslmode=require" -c "SELECT 1;" # 3 连通+认证
psql "host=${HOST} port=${PORT} dbname=${DB} user=${USER}" -c "\dt public.*" | head -5 # 4 权限
echo "全部检查通过"下一节
继续学习:SSL 连接初始化