BlueByte
FATAL: no pg_hba.conf entry for host (28000)Fixed

PostgreSQL: FATAL — no pg_hba.conf entry for host

By Haneul SeoUpdated September 18, 20267 min

Hi, it's BlueByte. A new app server, a container, or a laptop on the VPN tries to connect and PostgreSQL answers FATAL: no pg_hba.conf entry for host "10.0.2.15", user "app", database "shop", no encryption. The password is right, the database exists, the port is open — the server simply found no rule that lets this client in. We'll walk through what each part of the message tells you, the five ways a rule ends up missing, how to see the rules the server loaded, the fix per cause, and how to keep the next subnet from hitting the same wall.

What the message says, and what the last word tells you

The docs put it in one sentence: you succeeded in contacting the server, but it does not want to talk to you. The server walks pg_hba.conf top to bottom and uses the first line whose connection type, client address, database and user all match; there is no fall-through, and if no record matches, access is denied. The message quotes exactly what it tried to match, so read it literally:

psql: error: connection to server at "10.0.1.5", port 5432 failed: FATAL:  no pg_hba.conf entry for host "10.0.2.15", user "app", database "shop", no encryption

The last phrase is the connection's encryption state — in the server source it is one of no encryption, SSL encryption, or GSS encryption. Two siblings mean nearly the same: pg_hba.conf rejects connection for host … says a line did match and its method is reject; no pg_hba.conf entry for replication connection from host … is the same failure for a standby using the replication pseudo-database.

Five ways to end up with no matching line

The client's address is not covered. The most common case. pg_hba.conf allows 127.0.0.1/32 and the old subnet; the new app server, or a container on the Docker bridge at 172.17.0.3, is neither. The address in the message is what the server saw after any NAT — trust it over what you think the client's IP is.

The line exists, but for the other connection type. hostssl lines only match SSL connections, hostnossl only plain ones. The default sslmode for libpq is prefer: first try an SSL connection; if that fails, try a non-SSL connection. So when your only rule is hostssl and the SSL attempt is refused for another reason, the client retries in the clear, is refused again, and shows you the second error — ending in no encryption — while the log holds both.

localhost is not local. A local line matches Unix-domain sockets only; psql -h localhost is TCP and needs a host line for 127.0.0.1/32 — and localhost often resolves to ::1 first, which needs ::1/128.

Database or user do not match. A line for sameuser or a specific database will not match shop for user app; all in both columns does.

The file was edited but never reloaded, or the wrong file was edited. The server reads pg_hba.conf at start-up and on SIGHUP; saving the file changes nothing until you reload. Some distributions keep the file outside the data directory, so the one you edited may not be the one the server uses.

Read the rules the server actually sees

Connect any way that still works — usually the Unix socket as the postgres OS user — and ask the server which file it uses and how it parsed it:

SHOW hba_file;
SELECT rule_number, type, database, user_name, address, auth_method, error
FROM pg_hba_file_rules;
 rule_number | type  | database | user_name |  address  |  auth_method  | error
-------------+-------+----------+-----------+-----------+---------------+-------
           1 | local | {all}    | {all}     |           | peer          |
           2 | host  | {all}    | {all}     | 127.0.0.1 | scram-sha-256 |
           3 | host  | {all}    | {all}     | ::1       | scram-sha-256 |

Nothing covers 10.0.2.15 — cause one. A row with a non-null error is a typo that made the server skip that line. The view is superuser-only by default, and it shows the current contents of the file rather than what was last loaded — which is what makes it useful for testing an edit before you reload. The server log has one line per refused attempt, and the manual's tip is that it may say more than the client was told.

Fix 1: add a line for the client's real address, then reload

Add a line that matches the type, database, user and address from the message. Keep the CIDR as narrow as the deployment allows and use scram-sha-256:

# TYPE   DATABASE  USER  ADDRESS       METHOD
hostssl  shop      app   10.0.2.0/24   scram-sha-256

Order matters: put specific lines above broad ones, and above any reject. Then reload — no restart, no dropped sessions:

SELECT pg_reload_conf();
 pg_reload_conf
----------------
 t

pg_ctl reload, systemctl reload postgresql, or kill -HUP on the postmaster do the same. On Windows the manual notes new connections pick up the file without a reload.

Fix 2: match the connection type the client really uses

If the rule is hostssl and the message ends in no encryption, decide which side to move. To insist on TLS, stop the client falling back so the real error shows:

psql "host=10.0.1.5 dbname=shop user=app sslmode=require"

To allow both, use host instead of hostssl; it matches SSL and non-SSL alike. For the localhost case, add both loopback lines — 127.0.0.1/32 and ::1/128 — or drop -h so psql uses the socket and the local line.

Fix 3: the line is right but the server never re-read it

Compare SHOW hba_file with the path you edited. On Debian and Ubuntu packages it is /etc/postgresql/<version>/main/pg_hba.conf, not the data directory; in a container it is usually inside PGDATA. Edit the file the server names, then reload and watch the log:

sudo systemctl reload postgresql
sudo tail -n 3 /var/log/postgresql/postgresql-16-main.log
LOG:  received SIGHUP, reloading configuration files

If the log instead reports a syntax error followed by pg_hba.conf was not reloaded, the old rules stay in force until you fix the line pg_hba_file_rules points at.

A real case: a new app subnet and a "no encryption" that lied

A team moved their API from 10.0.1.0/24 to 10.0.2.0/24, and every pod failed with no pg_hba.conf entry for host "10.0.2.15", user "app", database "shop", no encryption. Their first guess was that the pods had lost TLS, because pg_hba.conf only had hostssl lines. The server log told the real story: each attempt produced two lines, the first ending in SSL encryption, the second in no encryption — the prefer fallback. Neither matched because no line covered 10.0.2.0/24 at all. They added hostssl shop app 10.0.2.0/24 scram-sha-256 above the old subnet's line, ran pg_reload_conf(), and set sslmode=require in the app so a future refusal shows once, with the right suffix.

Check it end to end and keep it from coming back

Connect from the client that failed and ask the server what it sees for this session:

SELECT inet_client_addr() AS client, s.ssl
FROM pg_stat_ssl s WHERE s.pid = pg_backend_pid();
  client   | ssl
-----------+-----
 10.0.2.15 | t

The address is the one from the error, and ssl is t if you came through a hostssl line. To keep this from recurring: add a pg_hba.conf line whenever you allocate a subnet, and keep the file in configuration management next to the firewall rules; query pg_hba_file_rules before every reload to catch typos; prefer hostssl with narrow CIDRs over host all all 0.0.0.0/0; and set sslmode=require in application connection strings so the message you get is the one that happened.

How this differs from "password authentication failed" and "Connection refused"

FATAL: password authentication failed for user "app" is one step later: a line matched, and the credentials failed the method it names — a password, .pgpass, or role problem, not pg_hba.conf. connection to server … failed: Connection refused is one step earlier: the client never reached PostgreSQL, so look at listen_addresses, the port, and the firewall. And FATAL: database "shop" does not exist means you are through authentication and the name is wrong.

Next time you hit this, walk these checks back in order: read the address and the last word of the message literally, list pg_hba_file_rules, add or fix the one line, reload, and confirm with inet_client_addr().

Related questions

I added the line and it still fails. What did I miss?

Four usual suspects: the server was not reloaded (pg_reload_conf() or systemctl reload); you edited a different file from the one SHOW hba_file names; an earlier line matched first — including a reject; or the client's address is not what you assumed (a Docker bridge or NAT address). Read the host in the message literally and check pg_hba_file_rules for an error column.

My client uses SSL, so why does the message end in "no encryption"?

With the default sslmode=prefer, libpq tries SSL first and, if that attempt is refused, retries without SSL and reports the second failure. The server log shows both attempts. Set sslmode=require on the client to stop the fallback and see the SSL attempt's own error.

Is host all all 0.0.0.0/0 scram-sha-256 an acceptable quick fix?

It works, and scram-sha-256 still requires a valid password, but it lets any address that can reach the port attempt authentication and it matches non-SSL connections. Use it only behind a firewall that already limits sources, and prefer hostssl with the specific CIDR in the message.

Why does psql -h localhost fail when plain psql works?

Plain psql connects over the Unix-domain socket and matches a local line; -h localhost is TCP and needs a host line for 127.0.0.1/32 — and often ::1/128, because localhost can resolve to IPv6 first. The message shows which address was tried.

Do I need to restart PostgreSQL after editing pg_hba.conf?

No. The file is read at start-up and on SIGHUP, so pg_reload_conf(), pg_ctl reload, or systemctl reload applies it without dropping sessions. On Windows the manual notes changes apply to new connections without a reload.

References

Haneul Seo

Infrastructure engineer · 10+ years running Linux fleets

More in this category

detected dubious ownershipFixed

Git: fatal: detected dubious ownership in repository

Since the CVE-2022-24765 fix in Git 2.35.2, Git refuses to read a repository whose working tree or .git directory is owned by a different user than the one running the command. It shows up in containers, CI jobs, sudo sessions and shared drives. Fix the ownership if the repo should be yours, or add the exact path to safe.directory in your global config — never in the repo's own config, which Git ignores for this.

Git
failed calling webhookFixed

Kubernetes: Internal error occurred: failed calling webhook

An admission webhook sits in front of your write, the API server could not get an answer out of it, and failurePolicy: Fail turned that silence into a rejection. The tail of the message is the whole diagnosis: context deadline exceeded means the call went nowhere, no endpoints available means nothing is running, and an x509 line means the API server does not trust the webhook's certificate. Each has a different fix, and none of them is your manifest.

Kubernetes
1205Fixed

MySQL: ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

A statement waited the full innodb_lock_wait_timeout for a row lock another transaction is still holding, and gave up. sys.innodb_lock_waits names the blocking session and hands you the KILL statement, and a blocking_query of NULL means the blocker is idle on an open transaction. The detail most retry loops get wrong: by default only the timed-out statement is rolled back, so your transaction is still open and still holds every lock it took earlier.

MySQL
MISCONFFixed

Redis: MISCONF Redis is configured to save RDB snapshots, but it's currently unable to persist to disk

Reads keep working and every write is rejected, because the last background save failed and stop-writes-on-bgsave-error defaults to yes. The log names the real cause — no space, a dir the redis user can't write, a read-only mount at rename time, or fork failing with Cannot allocate memory. Fix the cause, run one BGSAVE, and writes come back on their own with no restart: rdb_last_bgsave_status flips from err to ok. Setting stop-writes-on-bgsave-error no restores writes instantly but leaves the snapshot broken, so treat it as a deliberate trade, not the fix.

Redis
FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memoryFixed

Node.js: FATAL ERROR: Reached heap limit — JavaScript heap out of memory (exit 134)

The V8 heap has its own ceiling, derived from system memory and the Node release, and it is often far below the RAM you have; when a build or server reaches it, V8 aborts with FATAL ERROR: Reached heap limit and exit code 134. Read the real limit with v8.getHeapStatistics().heap_size_limit, then raise it with --max-old-space-size (in MiB) or NODE_OPTIONS for a large workload, size it below the cgroup limit inside containers, and use --heapsnapshot-near-heap-limit to catch a leak in a long-running process. Exit 137 with no FATAL ERROR line is a container kill, not this.

Node.js
exec /docker-entrypoint.sh: exec format errorFixed

Docker: "exec format error" when the container starts — wrong-platform image, no emulator, or a script with no shebang

The container exits on its first instruction with exec format error — the kernel's ENOEXEC, meaning the file exists but cannot be executed here. In practice that is an image built on one CPU architecture (an Apple-silicon Mac produces linux/arm64) and run on another (an x86_64 server) with no QEMU handler registered in binfmt_misc, or an entrypoint script whose first line is not a shebang. uname -m, docker image inspect and ls /proc/sys/fs/binfmt_misc tell the causes apart; the fix is an explicit docker buildx build --platform (or a manifest list for both), QEMU registration or --platform when you mean to emulate, and a #!/bin/sh line for the script.

Docker