/인프라/Fixing no pg_hba.conf entry and password authentication failed by Root Cause
InfrastructurePostgreSQLpg_hba.conf

Fixing no pg_hba.conf entry and password authentication failed by Root Cause

How to map six PostgreSQL connection errors onto the network, pg_hba, and authentication layers and diagnose them in 30 seconds. Covers an error-text lookup table, ss and pg_hba_file_rules diagnostics, first-match rules, PG14 scram-sha-256

Fixing no pg_hba.conf entry and password authentication failed by Root Cause

You waste 30 minutes because you read the error in the wrong order

In an incident, the two most common misdiagnosis patterns are:

  • You see FATAL: no pg_hba.conf entry for host ... and spend 30 minutes chasing firewalls and security groups.
  • You see could not connect to server: Connection refused and open pg_hba.conf to add 0.0.0.0/0 trust.

Both cases pick the wrong layer. A PostgreSQL connection has to pass the following three stages in order.

  1. TCP reachability — Does the client packet actually arrive at the socket postmaster is listening on?
  2. pg_hba rule match — Is there a line that matches the connection (TYPE/DB/USER/source IP)?
  3. Authentication — Do the credentials pass using the METHOD on the matched line?

The important fact: each stage produces a different message. The error text itself already tells you which stage failed. If you got Connection refused, the server has never even seen your request, so editing pg_hba.conf is 100% pointless. If you got password authentication failed, a pg_hba line already matched, so further pg_hba edits are a waste of time.

This post covers PostgreSQL 12–17 on Linux (Debian/Ubuntu and RHEL-family) plus Docker/Kubernetes/managed DB environments, and maps error text → layer → first command in 30 seconds.

Error-text lookup table: map six messages onto three layers

Find the error you are looking at in the table below. Run only that row’s “first check command.”

#Error textLayerFirst check commandCommon wrong turn
(a)psql: error: could not connect to server: Connection refused ... Is the server running on host "10.0.3.10" and accepting TCP/IP connections on port 5432?Networkss -lntp | grep 5432Editing pg_hba.conf (pointless)
(b)FATAL: no pg_hba.conf entry for host "10.0.3.51", user "app", database "prod", SSL offpg_hbaSELECT * FROM pg_hba_file_rules;Checking firewalls/security groups
(c)FATAL: no pg_hba.conf entry for host "10.0.3.51", user "app", database "prod", SSL onpg_hbaSame + check hostnossl lines“Do I need to turn SSL off?” rabbit hole
(d)FATAL: password authentication failed for user "app"AuthSELECT rolname, substring(rolpassword,1,4) FROM pg_authid;Re-editing pg_hba (already passed)
(e)psql: error: FATAL: Peer authentication failed for user "app"Auth + local socketpsql -h 127.0.0.1 -U app -d prodRepeatedly resetting the password
(f)FATAL: sorry, too many clients alreadySession slots (out of scope)SHOW max_connections;Network/pg_hba/auth already passed

A few interpretation rules to lock in.

(a) Network layer. The server process is down, listen_addresses is localhost, or a firewall/security group/container port mapping is blocking you. If the same symptom appears on local loopback, follow the general debug procedure in connection refused / ECONNREFUSED 127.0.0.1 30-second diagnostic runbook first. This post covers only PostgreSQL-specific items.

(b) and (c): SSL off / SSL on are not the cause — they are a record of how the client connected. The server is only stating “this connection was plaintext/TLS.” Interpretation is therefore the opposite of what people assume.

  • SSL off → the client connected in plaintext, but the server only has hostssl lines. → Give the client sslmode=require, or add a host line.
  • SSL on → the client connected with TLS, but you only have hostnossl, or the ADDRESS CIDR does not include the real source IP.

In both cases the conclusion is the same: “no matching line exists.” The difference is which TYPE of line you need to create.

(d) is proof that pg_hba already passed. Auth was attempted with the matched line’s METHOD (scram-sha-256/md5) and failed, so the only remaining branches are ① wrong password, ② hash-algorithm mismatch, or ③ the role does not exist.

(e) is connecting without -h, so you took the Unix-socket path. peer auth requires the OS account name and the DB role name to be the same. If switching to psql -h 127.0.0.1 changes the symptom, that is an immediate confirmation.

(f) is out of scope for this post. One diagnostic fact: this message means you already passed network, pg_hba, and authentication. For cause and recovery, see PostgreSQL too many clients already 30-second diagnosis and recovery runbook and PostgreSQL 'too many clients already': 5-minute diagnosis through a PgBouncer fix.

Three-layer diagnostic sequence: pin the failure with six commands

Once the table has narrowed the layer, run the following fixed sequence in order. Each command includes expected healthy output and the branch if it differs.

Step 1 — Check the listen socket with ss -lntp (run on the server)

Bash
sudo ss -lntp | grep 5432

Expected output (remote connections possible):

CODE
LISTEN 0  244  0.0.0.0:5432  0.0.0.0:*  users:(("postgres",pid=1234,fd=7))
LISTEN 0  244     [::]:5432     [::]:*  users:(("postgres",pid=1234,fd=7))

Branches:

  • Only 127.0.0.1:5432 is shown → remote access is confirmed impossible. listen_addresses='localhost', and no amount of pg_hba editing will stop error (a).
  • No lines at all → the server is down or on another port. systemctl status postgresql / journalctl -u postgresql -n 50.

Step 2 — Read the running process’s actual values

Do not eyeball config files; always query the live values. Changing listen_addresses and only reloading does not apply it, so file vs. runtime mismatch is common.

SQL
SHOW listen_addresses;
SHOW port;
SHOW hba_file;
SHOW config_file;

Expected output:

CODE
 listen_addresses
------------------
 *

 port
------
 5432

              hba_file
-------------------------------------
 /etc/postgresql/16/main/pg_hba.conf

Branch: if the hba_file path is not the file you edited, that itself is the cause. Package installs use /etc/postgresql/<major>/main/, source builds and RHEL-family use /var/lib/pgsql/<major>/data/, and the official Docker image uses /var/lib/postgresql/data/pg_hba.conf.

Step 3 — Attempt a real connection from remote

Bash
psql "host=10.0.3.10 port=5432 user=app dbname=prod sslmode=prefer" -c 'select 1'

Take the error text from this attempt and plug it back into the table in section 2. From this point on you are judging from the table, not guessing.

Step 4 — Confirm the actual source IP recorded in the server log

SQL
ALTER SYSTEM SET log_connections = on;
SELECT pg_reload_conf();

Log example:

CODE
2026-08-25 10:12:33 KST [2311] LOG:  connection received: host=10.0.7.88 port=51422
2026-08-25 10:12:33 KST [2311] FATAL:  no pg_hba.conf entry for host "10.0.7.88", user "app", database "prod", SSL off

Branch: if the IP the server logged is not the client IP you think you have, an LB/NAT/sidecar rewrote the source IP. Jump straight to “Environment-specific branches” below. When debugging IP-based rules, the IP in the log is the truth.

Step 5 — Inspect parsed rules via SQL (PG 10+)

SQL
SELECT line_number, type, database, user_name, address, netmask, auth_method, error
FROM pg_hba_file_rules
ORDER BY line_number;

Expected output:

CODE
 line_number |  type   | database | user_name |  address  |    netmask    |  auth_method   | error
-------------+---------+----------+-----------+-----------+---------------+----------------+-------
          89 | local   | {all}    | {all}     |           |               | peer           |
          92 | host    | {all}    | {all}     | 127.0.0.1 | 255.255.255.0 | scram-sha-256  |
          95 | host    | {prod}   | {app}     | 10.0.3.0  | 255.255.255.0 | scram-sha-256  |

Branch: if the error column is not NULL, that line was not loaded. Typical causes are typos, a bad CIDR, or a nonexistent METHOD. Make a habit of running:

SQL
SELECT line_number, error FROM pg_hba_file_rules WHERE error IS NOT NULL;

pg_hba.conf syntax and first-match: one broad reject line can disable everything

Line syntax:

CODE
TYPE  DATABASE  USER  ADDRESS  METHOD  [OPTIONS]

Meaning of TYPE values and how they connect to errors (b) and (c):

TYPEMatchesRelated error
localUnix domain socket (no -h)(e) Peer authentication failed
hostTCP, both plaintext and TLSCan fix both (b) and (c)
hostsslTLS connections onlyConnecting with SSL off produces (b)
hostnosslPlaintext connections onlyConnecting with TLS produces (c)

ADDRESS uses CIDR (10.0.3.0/24, 10.0.3.51/32); keywords all, samehost, and samenet are also allowed.

First-match-wins rule

PostgreSQL scans the file from the top, stops at the first matching line, and authenticates with that line’s METHOD. On failure it does not fall through to later lines. If you do not know this rule, you write a file like the one below and then spend hours on “I clearly added a line, why doesn’t it work?”

Before — a file where the lower line is ignored forever:

Config
# TYPE  DATABASE  USER  ADDRESS        METHOD
local   all       all                  peer
host    all       all   127.0.0.1/32   scram-sha-256
host    all       all   0.0.0.0/0      reject        # ← all remote connections terminate here
host    prod      app   10.0.3.0/24    scram-sha-256 # ← unreachable (dead rule)

After — specific allows first, catch-all reject last:

Config
# TYPE     DATABASE  USER  ADDRESS        METHOD
local      all       all                  peer
host       all       all   127.0.0.1/32   scram-sha-256

# Change 1: allow only the app-server subnet, TLS required (move the specific rule up)
hostssl    prod      app   10.0.3.0/24    scram-sha-256

# Change 2: explicitly block plaintext for the same target
hostnossl  prod      app   10.0.3.0/24    reject

# Change 3: catch-all reject must be the last line (moved from the old 3rd line)
host       all       all   0.0.0.0/0      reject

To spot dead rules by eye, compare pg_hba_file_rules line_number order against the rule above. Treat every allow line that sits below a broad reject as invalid.

METHOD decision table

METHODSecurity levelClient compatibilityRecommended use
trustNone (no auth)AllForbidden in production. Bootstrap only, and always ticket it
peerMedium (trust OS account)local onlyLocal server maintenance (postgres account)
identMediumNeeds an ident serverLegacy internal networks; not recommended for new setups
md5Low (legacy hash)All, including old JDBC/psycopg2Temporary use for old-driver compatibility
scram-sha-256HighPG 10+ server, modern driversDefault recommendation
certVery high (mTLS)Requires distributing client certsInternet-facing / regulated environments

PG 14 branch: you switched to scram but error (d) keeps happening

Starting with PostgreSQL 14, the default password_encryption is scram-sha-256. The catch: existing user passwords are still stored as md5 hashes. If you only change pg_hba to scram-sha-256, the stored hash and the auth method disagree and you get password authentication failed.

Diagnose:

SQL
SELECT rolname, substring(rolpassword, 1, 4) AS hash_prefix
FROM pg_authid
WHERE rolcanlogin;

Expected output:

CODE
 rolname  | hash_prefix
----------+-------------
 postgres | SCRA
 app      | md5          ← this row is the cause

Recover (reset the password so it is rehashed):

SQL
SET password_encryption = 'scram-sha-256';
ALTER USER app WITH PASSWORD 'new_password';

Verify — re-query until hash_prefix is SCRA, then test a real connection:

Bash
psql "host=10.0.3.10 user=app dbname=prod sslmode=require" -c 'select current_user'

SET is session-scoped, so for future accounts check password_encryption in postgresql.conf. If you still have old JDBC (below 9.4.12) or other drivers that do not support SCRAM, upgrading the driver is the real fix; md5 is only a temporary workaround.

Version differences

VersionDefault hashpg_hba-related features
9.6–13md5No include directive; pg_hba_file_rules from 10
14Switched to scram-sha-256Existing md5 users must be rehashed
15·16·17scram-sha-256include·include_if_exists·include_dir, regex matching (/^app_.*)

Regex matching example (PG 15+):

Config
hostssl  prod  "/^app_.*"  10.0.3.0/24  scram-sha-256

Environment-specific branches: Docker, Kubernetes, managed DB

EnvironmentWhat the source IP actually isKey settingsClassic trap
DockerContainer IP on the bridge network (172.17.0.0/16, etc.)listen_addresses='*' + -p 5432:5432127.0.0.1 inside the container is the container itself. POSTGRES_HOST_AUTH_METHOD=trust rewrites pg_hba wholesale at init
KubernetesPod CIDR, not the Service ClusterIPConfirm with kubectl cluster-info dump | grep -i cidr, then put that CIDR inSidecar/proxy hops rewrite the source IP to 127.0.0.1
RDS / Cloud SQLVPC internal IPpg_hba cannot be edited → security groups / authorized networks + rds.force_ssl=1 parameter groupWasting time looking for pg_hba. Access control is replaced by SGs

Confirm source IPs in Docker:

Bash
docker network inspect bridge --format '{{range .Containers}}{{.Name}} {{.IPv4Address}}{{println}}{{end}}'

Confirm Pod CIDR in Kubernetes:

Bash
kubectl cluster-info dump | grep -i -m2 'cluster-cidr'
# expected: --cluster-cidr=10.244.0.0/16

Put the confirmed range into pg_hba as-is.

Config
hostssl  prod  app  10.244.0.0/16  scram-sha-256

Applying changes: what reload is enough for vs. what needs a restart

Itemreloadrestart
Entire pg_hba.confNot needed
pg_ident.confNot needed
log_connections, log_min_duration_statementNot needed
listen_addresses✅ Required
port✅ Required
max_connections✅ Required
shared_buffers✅ Required

Run reload:

SQL
SELECT pg_reload_conf();
CODE
 pg_reload_conf
----------------
 t

Or from the shell:

Bash
sudo -u postgres pg_ctl reload -D /var/lib/pgsql/16/data
# or
sudo systemctl reload postgresql

Confirm application with both of these:

Bash
sudo tail -n 20 /var/log/postgresql/postgresql-16-main.log | grep -i sighup
# expected: LOG:  received SIGHUP, reloading configuration files
SQL
SELECT line_number, address, auth_method FROM pg_hba_file_rules WHERE error IS NULL;

If you got t but the new line is missing from pg_hba_file_rules, the file you edited is not the path from SHOW hba_file;.

Still broken after the fix: failure-branch tree

Branch ① psql works for me, but the app keeps failing

The connection pool is holding old connections or old settings. With HikariCP, connections are not recreated until maxLifetime (default 30 minutes) expires.

YAML
spring:
  datasource:
    hikari:
      max-lifetime: 900000      # 15 minutes
      keepalive-time: 300000

Confirmation is simple. If restarting the app makes it succeed immediately, the cause is pool cache. On the server, check who is actually connected:

SQL
SELECT client_addr, usename, state, backend_start
FROM pg_stat_activity
WHERE datname = 'prod'
ORDER BY backend_start DESC LIMIT 10;

Branch ② traffic goes through pgbouncer

In this case the real gate is not pg_hba but pgbouncer’s auth settings.

Bash
psql -h 127.0.0.1 -p 6432 -U pgbouncer pgbouncer -c 'SHOW CONFIG;' | grep -E 'auth_type|auth_file'
CODE
auth_type | scram-sha-256
auth_file | /etc/pgbouncer/userlist.txt

If the hash in userlist.txt disagrees with the DB’s rolpassword, no amount of pg_hba editing will help. After PG 14 scram migrations, forgetting to update this file at the same time is an especially common accident.

CODE
"app" "SCRAM-SHA-256$4096:...$...:..."

Branch ③ the source IP in the server log is unfamiliar

An LB, NAT, or service-mesh sidecar rewrote the source IP. You have two options.

  • Put the CIDR of the IP that appeared in the log into pg_hba as-is (fastest, but the range may get wider).
  • If you need to preserve the real source IP, look at proxy protocol or IP-preservation options on the proxy layer.

At that point IP-based rules are effectively useless, so it is more realistic to move control to auth strength (scram-sha-256 or cert) plus forced TLS.

Safety principles — lines you must not cross even when you are in a hurry

Config
# Never-do combination
host  all  all  0.0.0.0/0  trust
  • trust + 0.0.0.0/0 is unauthenticated, fully open access. Never leave it in production under any circumstances.
  • Keep CIDRs as tight as possible: /32 if you can, otherwise the application subnet.
  • Block plaintext explicitly with hostssl allow + hostnossl ... reject.
  • Temporary relaxations must be ticketed with an expiry date. A leftover temporary trust line that sits around for years is the incident type reported most often in practice.

For official docs, see the “Client Authentication” chapter in the PostgreSQL manual (pg_hba.conf File, Authentication Methods) together with the pg_hba_file_rules view description.

Conclusion: three-layer connection-error checklist

Error-text keywordLayerFirst command
Connection refused ... 5432Networkss -lntp | grep 5432
no pg_hba.conf entry ... SSL off/onpg_hbaSELECT * FROM pg_hba_file_rules;
password authentication failedAuthSELECT rolname, substring(rolpassword,1,4) FROM pg_authid;
Peer authentication failedAuth (local socket)Retry with psql -h 127.0.0.1 ...
too many clients alreadySession slotsSee the separate runbook

Two sentences to remember.

  1. Run ss -lntp before you open pg_hba.conf.
  2. If you got password authentication failed, pg_hba already passed.

Run the two lines below on your own server right now and check for latent errors. It is the cheapest way to find dead rules and parse errors before an incident.

SQL
SHOW hba_file;
SELECT line_number, error FROM pg_hba_file_rules WHERE error IS NOT NULL;

If you got this far and connections are still blocked, the remaining candidate is connection-slot exhaustion. Continue with PostgreSQL too many clients already 30-second diagnosis and recovery runbook.

FAQ

Q1. I got no pg_hba.conf entry ... SSL off. Should I turn SSL off on the server? No. SSL off is not the cause; it is a record that this connection was plaintext. You most likely only have hostssl lines on the server, so the correct response is to add sslmode=require to the client connection string, or add an appropriate host line on the server.

Q2. I edited pg_hba.conf but the change is not applying. Check three things in order. ① SHOW hba_file; — is the live path the file you edited? ② Does SELECT pg_reload_conf(); return t? ③ Does SELECT line_number, error FROM pg_hba_file_rules WHERE error IS NOT NULL; show any parse errors? Note that changing listen_addresses and port requires a restart, not a reload.

Q3. Where do I edit pg_hba.conf on RDS or Cloud SQL? You cannot edit pg_hba.conf on managed databases. Access control is done with security groups / authorized networks, and TLS enforcement with parameter-group settings such as rds.force_ssl=1. Auth strength is managed via password_encryption and resetting role passwords; confirm exact parameter names in each cloud vendor’s official docs.

확인 정보
✦ ✦ ✦
편집 검토 · Editorial Review

Nodelog는 모든 콘텐츠의 내용과 출처를 공개 전에 검토합니다. 환경(OS·버전)에 따라 결과가 달라질 수 있는 기술 정보는 공식 문서와 함께 확인하며, 검토 기준과 정정 원칙은 편집 정책에서 안내합니다. 오류를 발견하시면 이메일로 제보해 주세요 — 확인 후 신속히 정정합니다.

편집 책임 · Nodelog 기술 편집팀·발행 · ·업데이트 ·
관련 공식 문서PostgreSQL 공식 문서

Comments

Be the first to comment.