Audit PostgreSQL pg_hba.conf from the First Match, Not the Last Rule
PostgreSQL client authentication is decided by matching a connection against records in pg_hba.conf. The order is part of the security policy. The first record whose connection type, address, database, and role criteria match is the record that applies. A strict rule placed after a broad rule does not protect the broad rule's traffic.
The first matching record wins
The current PostgreSQL documentation describes the process directly: the first matching record is used to perform authentication. This makes a common configuration pattern risky. A general record for a network range can appear before a more specific rule for one database or role, even though a reader expects the more specific rule to win.
For example, a host all all 0.0.0.0/0 scram-sha-256 line followed by a stricter record for a management role does not make the management traffic use the later record. The broader line already matched. The same logic applies across local socket connections, TCP connections, replication roles, and specially named databases.
The host record type can match encrypted and unencrypted TCP connections. A team that needs to require TLS should use the relevant hostssl record instead of assuming that placing a password rule after a generic host rule creates transport protection.
Read the effective view
The server reads the file at startup and on configuration reload. The pg_hba_file_rules system view exposes parsed rules and errors in supported versions. Use it to find syntax problems, and keep the parser output next to the review evidence.
The review should cover the whole chain:
SELECT line_number, type, database, user_name, address,
auth_method, error
FROM pg_hba_file_rules
ORDER BY line_number;
Then map each expected client class to the record that will actually match. Include application hosts, administrators, replication clients, migration jobs, and local tooling. A rule with all in a role field needs an explicit owner because it can affect far more connections than its name suggests.
Test from the client side
A text review cannot prove network routing, DNS names, or role inheritance. Connect from a representative host with psql, inspect the authenticated role, and check pg_stat_ssl when transport matters. Use inet_client_addr() and related session functions to confirm the address the server sees, especially when a proxy or NAT layer changes the source.
Test a denied path as well. The ability to connect from an authorized host does not prove that a contractor network or an exposed public interface is blocked. Use a disposable database and role where possible. Do not weaken a production rule merely to make a test convenient.
Change safely
Put narrow, intentional records before broad defaults. Give each broad record a comment that states its purpose and owner. When possible, separate human administration from application roles and separate replication traffic from ordinary database connections.
After editing, validate syntax before reloading. Reload the configuration, then make a new connection. Existing sessions keep the authentication state they already have, so a successful old session is not evidence that the new rule works. Inspect the server log for the matching failure or success path and record the test time.
Finally, track exceptions so they can be removed. A temporary catch-all rule can survive long after the incident that prompted it. Review the file on a schedule and compare the effective records with the inventory of roles and client networks. The goal is a short, ordered explanation of every connection class, not a file that merely parses.
Top comments (0)