PostgreSQLV · Authentication, Roles and TLSAuthentication
pg_hba.conf: first match wins
What you'll learn
- Read a pg_hba.conf line and state exactly which connections it matches
- Predict the outcome when several rules could match one connection
- Use the server log to identify which rule actually matched
- Change the rule set without locking anybody out
Prerequisites
Practice
Verified against PostgreSQL 18.x · PostgreSQL (comparison targets) 17.11, 16.15 · PostgreSQL (support calendar) 18, 17, 16, 15, 14 supported · pgBackRest 2.59.1 · PgBouncer 1.25.2 · Patroni 4.1.5 · Ubuntu (host baseline) 26.04 LTS · 2026-08-27
pg_hba.conf is not a policy engine. It is an ordered list, and the
server stops reading at the first line that matches the connection in
front of it. That line decides the authentication method, and if that
method fails or the line says reject, the connection is refused. No
later line is consulted.
Almost every surprising access outcome in PostgreSQL follows from that single sentence.
The columns
# TYPE DATABASE USER ADDRESS METHOD [OPTIONS]
host all demo_scram 127.0.0.1/32 scram-sha-256
| Column | Matches |
|---|---|
TYPE | local (Unix socket), host (any), hostssl (TLS only), hostnossl (non-TLS only) |
DATABASE | A name, all, replication, a comma list, or @file |
USER | A role name, all, +groupname for members of a role, or a list |
ADDRESS | A CIDR, a hostname, all, samehost, samenet |
METHOD | trust, reject, scram-sha-256, md5, peer, cert, ldap, gss, oauth, and others |
All four of the first columns must match for the line to apply.
+groupname is worth noting: it matches any role that is a member of
that role, which is how you write rules against a group rather than
enumerating individuals.
First match wins, demonstrated
Two experiments on the same live cluster, differing only in the order of two lines.
Order A — the broad rule above the specific one:
local all all trust
host all all 127.0.0.1/32 reject
host all demo_scram 127.0.0.1/32 scram-sha-256
$ PGPASSWORD=... psql -h 127.0.0.1 -U demo_scram -d postgres -c 'SELECT 1'psql: error: connection to server at "127.0.0.1", port 5432 failed:
FATAL: pg_hba.conf rejects connection for host "127.0.0.1", user "demo_scram",
database "postgres", no encryptionOrder B — the same three rules, specific one first:
local all all trust
host all demo_scram 127.0.0.1/32 scram-sha-256
host all all 127.0.0.1/32 reject
$ PGPASSWORD=... psql -h 127.0.0.1 -U demo_scram -d postgres -tAc 'SELECT current_user'demo_scramSame rules, same connection, opposite outcomes. That is the whole lesson, and it is why reasoning about this file by eye is unreliable once it exceeds a handful of lines.
Making the evaluation observable
Two mechanisms remove the guesswork.
The connection log names the matching line. With
log_connections enabled, PostgreSQL records the file and line number
of the rule that authenticated the connection:
$ docker logs rbpg-auth | grep 'connection authenticated'LOG: connection authenticated: user="postgres" method=trust (/var/lib/postgresql/18/docker/pg_hba.conf:2)
LOG: connection authenticated: identity="demo_scram" method=scram-sha-256 (/var/lib/postgresql/18/docker/pg_hba.conf:2)pg_hba_file_rules shows the parsed rule set, in order, with any
parse errors, without reloading:
psql -U postgres -c \
"SELECT rule_number, line_number, type, database, user_name,
address, auth_method, error
FROM pg_hba_file_rules ORDER BY rule_number"
The error column is the important one. It reports a line the server
could not parse before you reload, which turns a potential lockout
into a query result. Checking it is the single most valuable habit in
this lesson.
Here it is catching a real mistake — a peer method on a host line,
which cannot work because a TCP connection carries no operating-system
identity:
$ psql -U postgres -c 'SELECT line_number, type, auth_method, error FROM pg_hba_file_rules WHERE error IS NOT NULL' line_number | type | auth_method | error
-------------+------+-------------+--------------------------------------------------------
4 | | | peer authentication is only supported on local sockets
(1 row)Note that type and auth_method are empty for a line that failed to
parse: the server could not interpret it, so it has nothing to report
about it beyond the error and the line number. An empty result from
this query is the pass condition.
Changing the rules safely
pg_hba.conf is re-read on reload, so changes are cheap to apply and
cheap to get wrong.
# 1. Keep a copy you can restore in one step
cp "$(psql -U postgres -tAc 'SHOW hba_file')" /var/backups/pg_hba.conf.$(date -u +%Y%m%dT%H%M%SZ)
# 2. Edit, then check the parse BEFORE reloading
psql -U postgres -c \
"SELECT line_number, type, database, user_name, address, auth_method, error
FROM pg_hba_file_rules WHERE error IS NOT NULL"
# 3. Reload
psql -U postgres -c 'SELECT pg_reload_conf()'
# 4. Confirm the server accepted it
psql -U postgres -c \
"SELECT count(*) AS rules_with_errors FROM pg_hba_file_rules WHERE error IS NOT NULL"
# 5. Test the connection you changed, from where it actually comes from
Step 2 is what prevents a lockout: pg_hba_file_rules reads the file
from disk and reports parse errors while the server is still running
the previous, working rule set.
What the official container image ships
Worth knowing, because it is what most people meet first:
$ grep -vE '^#|^$' $PGDATA/pg_hba.conflocal all all trust
host all all 127.0.0.1/32 trust
host all all ::1/128 trust
local replication all trust
host replication all 127.0.0.1/32 trust
host replication all ::1/128 trust
host all all all scram-sha-256Anything arriving over the loopback interface or the Unix socket is trusted without a password. Connections from elsewhere need SCRAM.
For a disposable lab container that is a sensible default. It becomes a problem when the same image is used for something long-lived and another process on the same host — or another container sharing the network namespace — can reach the loopback address.
Production discipline
- Order the file from most specific to most general. A broad rule above a specific one makes the specific one dead code.
- Check
pg_hba_file_rulesfor errors before every reload. It reads the file while the server still runs the working rule set. - Enable
log_connectionsso the matching rule is recorded. The file and line number settle disputes that otherwise become arguments. - Keep a
localpeerrule as the safety rope, so a mistake in the network rules does not lock you out of the host. - Never widen the method to diagnose. Widen the address on a rule that still authenticates, and only temporarily.
- Copy the file before editing it. Restoring is then one
cpand a reload.
Cross-course references
- Secrets, PKI & Certificate Management — Part VII (TLS for
operators) covers what
hostsslactually requires of the client, and Part XI (SSH) covers the analogous ordered-rule reasoning insshd_config. - Linux for Production Sysadmins — Part XXV (Firewall) covers the same first-match-wins semantics in nftables, and Part XXVI (SSH) covers keeping an out-of-band route in before changing access rules.
- Ansible for Production Sysadmins — Part XVII (Templates) covers generating this file from a source of truth, which is how the ordering stops being accidental.
Quiz
Knowledge check · 6 questions
Q1. A pg_hba.conf contains a reject rule for 10.0.0.0/8 on line 4 and a scram-sha-256 rule for the same range on line 9. A client from 10.2.3.4 connects. What happens?
Q2. Which check should be run after editing pg_hba.conf but before reloading?
Q3. Which statements about pg_hba.conf are correct? Select all that apply.
Q4. The official postgres container image ships a pg_hba.conf that trusts connections from the loopback address without a password.
Q5. Distinguish the errors 'pg_hba.conf rejects connection for host' and 'no pg_hba.conf entry for host', and say what each calls for.
Q6. Explain the failure and give the safe remediation sequence.
A change added monitoring access by inserting a line at the top of pg_hba.conf reading 'host all all 10.0.0.0/8 trust', then reloading. Monitoring began working. Three weeks later a security review finds that four application services which had been configured for scram-sha-256 are connecting without presenting a password, and that a decommissioned host still holds valid connectivity. The applications have reported no errors at any point.
Passing score: 75%. Answers are checked in this browser.