Skip to content

Log PostgreSQL connections and authentication

Severity: lowApplies to: PostgreSQL 14+Applies to: PostgreSQL 18
The fixpostgresql.conf
Terminal window
log_connections = on
log_disconnections = on
log_hostname = off
log_line_prefix = '%m [%p] %q%u@%d from %h '
SELECT pg_reload_conf();

This closes no exposure, which is why it is low and last. It changes what you can find out afterwards.

Without log_connections, a successful authentication leaves no trace. You cannot answer “who connected, from where, and how” — not during an incident, not during an audit, and not when you’re trying to work out which client is still on plaintext or still using md5. The log line names the authentication method, which makes it the most reliable way to confirm the migrations on the other pages in this cluster actually landed.

log_disconnections adds session duration, which is what turns a list of logins into something you can spot anomalies in.

log_hostname = on looks helpful — hostnames are friendlier than addresses. It performs a reverse DNS lookup on every connection, synchronously.

On a busy server that is a per-connection DNS round trip in the connection path, and if the resolver is slow or unreachable, connection latency follows it. Worse, the resulting name is attacker-influenced: reverse DNS is controlled by whoever owns the address, so a hostname in your logs is not evidence of anything.

Leave it off and log addresses. %h in log_line_prefix gives you the client address, which is both faster and more truthful.

log_statement = ‘all’ will log your passwords

Section titled “log_statement = ‘all’ will log your passwords”

The other common suggestion is log_statement = 'all'. Understand what it does before enabling it: it writes every SQL statement to the log, including

ALTER ROLE myapp WITH PASSWORD 'the-actual-secret';

in cleartext. It logs your data too — every value in every INSERT. You have moved sensitive data into a file with different permissions, different retention and different backup rules than the database, and probably shipped it to a log aggregator.

For an audit trail of changes, log_statement = 'ddl' is the defensible middle ground: schema changes without the data or the credentials. For real auditing, use the pgaudit extension, which is designed for it and can exclude sensitive statements.