PostgreSQL数据库修改管理员密码的方法有哪些
作者:liuziyi
如果你忘记了PostgreSQL密码怎么办,别慌,下面小编就教大家如何利用pg_hba.conf的trust规则和peer认证,在无需重启数据库的情况下安全修改密码吧
版本兼容矩阵
| 版本 | 关键差异 |
|---|---|
| PG 9.6 及更早 | password_encryption 为布尔值 on/off,只支持 MD5,无 SCRAM |
| PG 10 ~ 13 | password_encryption 为枚举值,默认 md5,可切 scram-sha-256 |
| PG 14 ~ 17 | password_encryption 默认 scram-sha-256 |
| PG 18 | 同上,md5 仍可显式指定但已不推荐 |
| PG 9.4+ | pg_hba.conf 支持 include / include_if_exists / include_dir |
脚本
#!/usr/bin/env bash
# ============================================================
# PostgreSQL 管理员密码修改工具
# 用法:bash pg_chpass.sh --help
# ============================================================
set -uo pipefail
SCRIPT_NAME=$(basename "$0")
SCRIPT_VERSION="1.0.0"
usage(){
cat <<EOF
${SCRIPT_NAME} ${SCRIPT_VERSION} — PostgreSQL 管理员密码修改工具
用法:
bash ${SCRIPT_NAME} [选项]
选项:
-h, --help 显示本帮助并退出(不连接数据库)
-V, --version 显示脚本版本并退出
-u, --user <角色名> 要改密的角色,默认 postgres
-p, --password <新密码> 新密码;不提供则交互输入(推荐)
--old-password <旧密码>
已知旧密码时直接认证,避免修改 pg_hba.conf
-H, --host <主机> 连接主机,默认 127.0.0.1
-P, --port <端口> 连接端口,默认 5432
-d, --database <库名> 连接库,默认 postgres
-D, --datadir <数据目录> 指定 PGDATA;不指定则自动探测
--hba <文件> 指定 pg_hba.conf;不指定则自动探测
--no-hba-edit 禁止修改 pg_hba.conf,只用 peer 或旧密码认证
--no-verify 跳过改密后的登录验证
--dry-run 只打印将要执行的动作,不实际执行
-- 结束选项解析
环境变量:
PG_NEW_PASS 等价于 --password
PG_OLD_PASS 等价于 --old-password
PGPORT / PGDATA 未通过选项指定时使用
认证路径(按顺序自动尝试):
1. 本地 peer 认证:以操作系统用户 postgres 直连,不需要密码
2. 旧密码认证:使用 --old-password 通过 TCP 连接
3. trust 兜底:临时在 pg_hba.conf 顶部插入 trust 规则并 reload
版本兼容:
PG 9.6 及更早 password_encryption 为布尔值,仅支持 MD5
PG 10 ~ 13 枚举值,默认 md5,可切 scram-sha-256
PG 14 ~ 18 默认 scram-sha-256
脚本会读取 pg_hba.conf 中实际使用的认证方法,自动对齐加密算法
执行前提:
1. 建议以 root 或 postgres 用户执行
2. 改密本身不需要重启,只做 reload,业务不中断
3. trust 窗口期仅对本机开放,脚本结束会自动还原 pg_hba.conf
4. 流复制环境中修改复制用户密码后,需同步更新备库 primary_conninfo
退出码:
0 成功
1 执行失败
2 参数错误
示例:
bash ${SCRIPT_NAME} --help
bash ${SCRIPT_NAME} -u postgres
bash ${SCRIPT_NAME} -u postgres -p 'N3wP@ssw0rd'
bash ${SCRIPT_NAME} -u postgres --old-password 'oldpass'
bash ${SCRIPT_NAME} -u postgres --no-hba-edit
bash ${SCRIPT_NAME} --dry-run
安全提示:
使用 --password 或位置参数时,密码会出现在 ps 与 shell 历史中;
生产环境建议交互输入,或改用 PG_NEW_PASS 环境变量。
EOF
}
err(){ echo "[错误] $*" >&2; }
info(){ echo "[信息] $*"; }
warn(){ echo "[警告] $*" >&2; }
# ---------- 参数解析 ----------
DB_USER="postgres"
NEW_PASS="${PG_NEW_PASS:-}"
OLD_PASS="${PG_OLD_PASS:-}"
PGHOST="${PGHOST:-127.0.0.1}"
PGPORT="${PGPORT:-5432}"
PGDATABASE="postgres"
DATADIR="${PGDATA:-}"
HBA_FILE=""
EDIT_HBA=1
DO_VERIFY=1
DRY_RUN=0
while [ $# -gt 0 ]; do
case "$1" in
-h|--help) usage; exit 0 ;;
-V|--version) echo "${SCRIPT_NAME} ${SCRIPT_VERSION}"; exit 0 ;;
-u|--user) [ $# -ge 2 ] || { err "$1 需要参数"; exit 2; }; DB_USER="$2"; shift 2 ;;
--user=*) DB_USER="${1#*=}"; shift ;;
-p|--password) [ $# -ge 2 ] || { err "$1 需要参数"; exit 2; }; NEW_PASS="$2"; shift 2 ;;
--password=*) NEW_PASS="${1#*=}"; shift ;;
--old-password) [ $# -ge 2 ] || { err "$1 需要参数"; exit 2; }; OLD_PASS="$2"; shift 2 ;;
--old-password=*) OLD_PASS="${1#*=}"; shift ;;
-H|--host) [ $# -ge 2 ] || { err "$1 需要参数"; exit 2; }; PGHOST="$2"; shift 2 ;;
--host=*) PGHOST="${1#*=}"; shift ;;
-P|--port) [ $# -ge 2 ] || { err "$1 需要参数"; exit 2; }; PGPORT="$2"; shift 2 ;;
--port=*) PGPORT="${1#*=}"; shift ;;
-d|--database) [ $# -ge 2 ] || { err "$1 需要参数"; exit 2; }; PGDATABASE="$2"; shift 2 ;;
--database=*) PGDATABASE="${1#*=}"; shift ;;
-D|--datadir) [ $# -ge 2 ] || { err "$1 需要参数"; exit 2; }; DATADIR="$2"; shift 2 ;;
--datadir=*) DATADIR="${1#*=}"; shift ;;
--hba) [ $# -ge 2 ] || { err "$1 需要参数"; exit 2; }; HBA_FILE="$2"; shift 2 ;;
--hba=*) HBA_FILE="${1#*=}"; shift ;;
--no-hba-edit) EDIT_HBA=0; shift ;;
--no-verify) DO_VERIFY=0; shift ;;
--dry-run) DRY_RUN=1; shift ;;
--) shift; break ;;
-*) err "未知选项:$1"; echo "使用 ${SCRIPT_NAME} --help 查看帮助" >&2; exit 2 ;;
*) err "多余的参数:$1"; echo "使用 ${SCRIPT_NAME} --help 查看帮助" >&2; exit 2 ;;
esac
done
[ $# -gt 0 ] && { err "多余的参数:$*"; exit 2; }
# ---------- 密码输入 ----------
if [ -z "$NEW_PASS" ]; then
printf "请输入角色 %s 的新密码:" "$DB_USER"
stty -echo 2>/dev/null
read -r NEW_PASS
stty echo 2>/dev/null
echo
fi
[ -z "$NEW_PASS" ] && { err "新密码不能为空"; exit 2; }
case "$DB_USER" in
*'"'*|*"'"*|*" "*) err "角色名不合法"; exit 2 ;;
esac
# SQL 单引号转义
NEW_PASS_SQL=$(printf '%s' "$NEW_PASS" | sed "s/'/''/g")
# ---------- 定位可执行文件 ----------
PSQL=$(command -v psql 2>/dev/null || true)
if [ -z "$PSQL" ]; then
PSQL=$(ls -1 /usr/pgsql-*/bin/psql /usr/lib/postgresql/*/bin/psql \
/usr/local/pgsql/bin/psql 2>/dev/null | sort -V | tail -1)
fi
[ -n "$PSQL" ] && [ -x "$PSQL" ] || { err "未找到 psql,请确认 PostgreSQL 已安装"; exit 1; }
PG_CTL=$(dirname "$PSQL")/pg_ctl
[ -x "$PG_CTL" ] || PG_CTL=$(command -v pg_ctl 2>/dev/null || true)
# ---------- 判定执行身份 ----------
AS_POSTGRES=0
if [ "$(id -u)" -eq 0 ] && id postgres >/dev/null 2>&1; then
AS_POSTGRES=1
fi
pg_exec_sql(){
local sql="$1"; shift
local cmd="$PSQL -X -q -t -A -v ON_ERROR_STOP=1 $* -f -"
if [ "$AS_POSTGRES" -eq 1 ]; then
printf '%s\n' "$sql" | su - postgres -s /bin/bash -c "$cmd"
else
printf '%s\n' "$sql" | bash -c "$cmd"
fi
}
psql_direct(){
local sql="$1"; shift
printf '%s\n' "$sql" | "$PSQL" -X -q -t -A -v ON_ERROR_STOP=1 "$@" -f -
}
# ---------- 探测数据目录 ----------
detect_datadir(){
local d
d=$(ps -eo args= 2>/dev/null | grep -E '[p]ostgres' \
| sed -n 's/.*-D[[:space:]]*\([^[:space:]]*\).*/\1/p' | head -1)
[ -n "$d" ] && [ -f "$d/PG_VERSION" ] && { echo "$d"; return 0; }
for d in /var/lib/pgsql/data /var/lib/pgsql/*/data \
/var/lib/postgresql/*/main /usr/local/pgsql/data \
/data/pgdata /data/postgres; do
[ -f "$d/PG_VERSION" ] && { echo "$d"; return 0; }
done
return 1
}
if [ -z "$DATADIR" ]; then
DATADIR=$(detect_datadir) || true
fi
# ---------- 重载配置 ----------
reload_pg(){
if [ -n "$PG_CTL" ] && [ -n "$DATADIR" ] && [ -d "$DATADIR" ]; then
if [ "$AS_POSTGRES" -eq 1 ]; then
su - postgres -s /bin/bash -c \
"'$PG_CTL' reload -D '$DATADIR'" >/dev/null 2>&1 && return 0
else
"$PG_CTL" reload -D "$DATADIR" >/dev/null 2>&1 && return 0
fi
fi
if command -v systemctl >/dev/null 2>&1; then
systemctl reload postgresql >/dev/null 2>&1 && return 0
systemctl reload postgresql-* >/dev/null 2>&1 && return 0
fi
return 1
}
# ---------- 认证路径 1:peer ----------
CONN_MODE=""
try_peer(){
local out
out=$(pg_exec_sql "SELECT 'PEER_OK';" -d "$PGDATABASE" 2>/dev/null) || return 1
case "$out" in *PEER_OK*) CONN_MODE="peer"; return 0 ;; esac
return 1
}
# ---------- 认证路径 2:旧密码 ----------
try_oldpw(){
[ -z "$OLD_PASS" ] && return 1
local out
out=$(PGPASSWORD="$OLD_PASS" psql_direct "SELECT 'OLD_OK';" \
-h "$PGHOST" -p "$PGPORT" -U "$DB_USER" -d "$PGDATABASE" 2>/dev/null) || return 1
case "$out" in *OLD_OK*) CONN_MODE="pw"; return 0 ;; esac
return 1
}
exec_val(){
local sql="$1"
case "$CONN_MODE" in
peer) pg_exec_sql "$sql" -d "$PGDATABASE" ;;
*) psql_direct "$sql" -h "$PGHOST" -p "$PGPORT" -U "$DB_USER" -d "$PGDATABASE" ;;
esac
}
# ---------- 主流程 ----------
info "角色:$DB_USER 连接:$PGHOST:$PGPORT/$PGDATABASE"
if [ "$DRY_RUN" -eq 1 ]; then
echo "----- 将执行(dry-run,未实际执行)-----"
echo "ALTER USER \"$DB_USER\" WITH PASSWORD '******';"
[ "$EDIT_HBA" -eq 1 ] && echo "必要时临时修改 pg_hba.conf 并 reload(不改动 restart)"
echo "---------------------------------------"
exit 0
fi
if try_peer; then
info "认证方式:本地 peer 认证(无需密码)"
elif try_oldpw; then
info "认证方式:旧密码认证"
else
if [ "$EDIT_HBA" -eq 0 ]; then
err "peer 与旧密码认证均失败,且已禁用 pg_hba.conf 修改"
exit 1
fi
warn "常规认证失败,准备临时启用 trust 认证"
if [ -z "$DATADIR" ] || [ ! -d "$DATADIR" ]; then
err "未定位到数据目录,请用 -D 指定 PGDATA"
exit 1
fi
[ -n "$HBA_FILE" ] || HBA_FILE="$DATADIR/pg_hba.conf"
[ -f "$HBA_FILE" ] || { err "未找到 pg_hba.conf:$HBA_FILE"; exit 1; }
HBA_BAK="${HBA_FILE}.bak.$(date +%Y%m%d%H%M%S)"
cp -a "$HBA_FILE" "$HBA_BAK" || { err "备份 pg_hba.conf 失败"; exit 1; }
info "pg_hba.conf 已备份:$HBA_BAK"
RESTORED=0
restore_hba(){
[ "$RESTORED" -eq 1 ] && return 0
RESTORED=1
info "还原 pg_hba.conf..."
cp -a "$HBA_BAK" "$HBA_FILE"
reload_pg && info "已 reload" || warn "reload 失败,请手动执行 pg_ctl reload"
}
trap 'restore_hba; exit 1' INT TERM
{
echo "# ==== temporary password reset $(date '+%F %T') ===="
echo "local all all trust"
echo "host all all 127.0.0.1/32 trust"
echo "host all all ::1/128 trust"
cat "$HBA_BAK"
} > "$HBA_FILE"
chown --reference="$HBA_BAK" "$HBA_FILE" 2>/dev/null || true
chmod --reference="$HBA_BAK" "$HBA_FILE" 2>/dev/null || true
if ! reload_pg; then
err "reload 失败,请检查数据目录权限"
restore_hba
exit 1
fi
sleep 2
CONN_MODE="trust"
if ! psql_direct "SELECT 'TRUST_OK';" -h "$PGHOST" -p "$PGPORT" \
-U "$DB_USER" -d "$PGDATABASE" >/dev/null 2>&1; then
err "trust 模式下仍无法连接,请检查 listen_addresses 与端口"
restore_hba
exit 1
fi
info "认证方式:临时 trust"
fi
# ---------- 读取版本与配置 ----------
VER_NUM=$(exec_val "SHOW server_version_num;" 2>/dev/null | tr -dc '0-9')
VER=$(exec_val "SHOW server_version;" 2>/dev/null | tr -d '[:space:]')
[ -n "$VER" ] && info "版本:$VER"
if [ -z "$HBA_FILE" ]; then
HBA_FILE=$(exec_val "SHOW hba_file;" 2>/dev/null | tr -d '[:space:]')
fi
# ---------- 对齐加密算法 ----------
ENC_SQL=""
if [ -n "$VER_NUM" ] && [ "$VER_NUM" -ge 100000 ]; then
AUTH_METHOD=""
if [ -n "$HBA_FILE" ] && [ -f "$HBA_FILE" ]; then
if grep -qiE '^[[:space:]]*(host|hostssl|hostnossl)[^#]*scram-sha-256' "$HBA_FILE"; then
AUTH_METHOD="scram-sha-256"
elif grep -qiE '^[[:space:]]*(host|hostssl|hostnossl)[^#]*md5' "$HBA_FILE"; then
AUTH_METHOD="md5"
fi
fi
if [ -z "$AUTH_METHOD" ]; then
if [ "$VER_NUM" -ge 140000 ]; then AUTH_METHOD="scram-sha-256"; else AUTH_METHOD="md5"; fi
fi
ENC_SQL="SET password_encryption = '$AUTH_METHOD';"
info "加密算法:$AUTH_METHOD(依据 pg_hba.conf 与版本自动对齐)"
fi
# ---------- 执行改密 ----------
ALTER_SQL="${ENC_SQL}
ALTER USER \"$DB_USER\" WITH PASSWORD '$NEW_PASS_SQL';
SELECT 'ALTER_OK';"
OUT=$(exec_val "$ALTER_SQL" 2>&1)
RC=$?
printf '%s\n' "$OUT" | grep -v '^ALTER_OK$' | grep -v '^SET$' || true
if [ "$RC" -ne 0 ] || ! printf '%s\n' "$OUT" | grep -q 'ALTER_OK'; then
err "改密失败,请根据上面的错误定位"
case "$OUT" in
*"does not exist"*) warn "角色不存在,请用 -u 指定正确的角色名" ;;
*"must be superuser"*) warn "当前连接角色不是超级用户,需用 postgres 或具备 CREATEROLE 权限的角色" ;;
*"password authentication failed"*) warn "旧密码错误" ;;
esac
exit 1
fi
info "ALTER USER 执行成功"
# ---------- 还原 pg_hba.conf ----------
if [ "${RESTORED:-0}" -eq 0 ] && [ -n "${HBA_BAK:-}" ]; then
restore_hba
trap - INT TERM
fi
# ---------- 验证 ----------
if [ "$DO_VERIFY" -eq 1 ]; then
if [ -n "$HBA_FILE" ] && [ -f "$HBA_FILE" ] && \
grep -qE '^[[:space:]]*host[^#]*127\.0\.0\.1' "$HBA_FILE"; then
info "验证新密码登录..."
if PGPASSWORD="$NEW_PASS" "$PSQL" -X -q -t -A -h "$PGHOST" -p "$PGPORT" \
-U "$DB_USER" -d "$PGDATABASE" -c "SELECT 'VERIFY_OK';" 2>/dev/null \
| grep -q 'VERIFY_OK'; then
info "验证通过:新密码可以正常登录"
else
warn "新密码验证失败。若 pg_hba.conf 使用 md5 而密码以 scram 存储(或反之),请检查 password_encryption 设置"
exit 1
fi
else
warn "pg_hba.conf 中未找到 127.0.0.1 的 host 条目,跳过 TCP 验证"
warn "可手动执行:psql -h $PGHOST -p $PGPORT -U $DB_USER -d $PGDATABASE"
fi
fi
info "完成"
exit 0使用示例
bash pg_chpass.sh --help bash pg_chpass.sh -u postgres bash pg_chpass.sh -u postgres -p 'N3wP@ssw0rd' bash pg_chpass.sh -u postgres --old-password 'oldpass' bash pg_chpass.sh -u postgres --no-hba-edit bash pg_chpass.sh --dry-run
关键点说明
| 项目 | 说明 |
|---|---|
| 不需要重启 | PG 改密和 pg_hba.conf 变更都只需 reload(pg_ctl reload 或 SELECT pg_reload_conf()),业务连接不受影响 |
| peer 认证优先 | Linux 默认 local all all peer,以 postgres 系统用户登录完全不需要密码,这是最干净的路径,脚本首选 |
| trust 规则必须放顶部 | pg_hba.conf 是自上而下首条匹配生效,临时规则必须插到文件开头,追加到末尾会被原有规则抢先匹配而无效 |
| 只开放本机 | 临时规则仅限 local、127.0.0.1/32、::1/128,不对外网开放,窗口期极短 |
| 算法自动对齐 | 脚本读取 pg_hba.conf 中实际的 md5 / scram-sha-256,用 SET password_encryption 在会话级对齐,避免改完登不上 |
| 会话级设置 | 使用 SET password_encryption 而非 ALTER SYSTEM,不改全局配置,无需额外 reload |
| 角色名与密码转义 | 角色名用双引号包裹,密码中的单引号自动转义为 '' |
| 文件属主还原 | 用 chown --reference / chmod --reference 保持 pg_hba.conf 原有属主与权限,避免 postgres 无法读取 |
| 中断保护 | trap 确保 Ctrl+C 时也还原 pg_hba.conf 并 reload |
高风险提示
- 流复制环境:修改复制用户(通常为
replicator)密码后,备库的primary_conninfo中保存的旧密码会导致复制中断,必须同步更新并reload备库。若使用pgpass文件,也要同步修改。 pg_hba.conf使用include_dir:PG 9.4+ 支持拆分配置,临时规则插在主文件顶部通常仍能生效(主文件内容优先),但若主文件本身只有include行,需确认插入位置确实在 include 之前。listen_addresses未包含 127.0.0.1:此时 trust 兜底路径会连不上,脚本会报错并自动还原,不会留下不安全状态。- 连接池与中间件:改密后 PgBouncer、应用连接池、监控采集、备份脚本中的旧密码都要同步更新,否则会出现批量认证失败。
- PG 9.6 及更早:
password_encryption是布尔值,脚本会自动跳过SET password_encryption语句,按 MD5 处理。 - 国产兼容分支:openGauss、GaussDB、KingbaseES 等虽沿用
pg_hba.conf与ALTER USER语法,但在角色体系、密码策略函数、三权分立模式上存在差异,本脚本不保证直接可用,需按目标产品核对。
到此这篇关于PostgreSQL数据库修改管理员密码的方法有哪些的文章就介绍到这了,更多相关PostgreSQL修改管理员密码内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
