Database audit fix

How to Secure PostgreSQL listen_addresses Binding

PostgreSQL takes the interfaces it listens on from listen_addresses, which defaults to localhost but is set to * by most container images. Restrict it to localhost or one private address, then restart, because it is not reloadable.

Hiren KalariyaLast reviewed: Sep 25, 2026Check postgresql-network-binding
High
severity
Yes
needs root or sudo
5
results it can return
3 of 7
checks in this audit

Every threshold on this page is transcribed from the database-security-network-isolation audit script that CtrlOps runs, and a build check fails if the two ever disagree. See all 7 Network Isolation checks, or how the audit runs.

High severityNeeds sudo

What this check reads

With a working postgres session (PGOK=1) it asks the running server directly with SHOW listen_addresses. Otherwise, as root, it takes the first file matched by ls /etc/postgresql/*/main/postgresql.conf /var/lib/pgsql/data/postgresql.conf /var/lib/pgsql/*/data/postgresql.conf, keeps the last listen_addresses line in it and strips the surrounding quotes. localhost, 127.0.0.1 and an empty value all pass, an empty value meaning Unix socket only, and on the config path a missing directive passes too because PostgreSQL's own default is localhost. Only * or a value containing 0.0.0.0 produces a WARN; any other specific address passes with the message asking you to confirm each one is intended.

When it applies

Runs only when PostgreSQL is detected (HAS_PG=1). With PGOK=1 it asks the running server (SHOW listen_addresses) - authoritative. Without it but with root, it greps the first postgresql.conf found under /etc/postgresql/*/main/ or /var/lib/pgsql/, which on a multi-cluster host may not be the cluster you care about. Unprivileged, it SKIPs. Note two deliberate behaviours: an empty listen_addresses is a PASS (Unix socket only), and any specific non-loopback address is also a PASS, with the message asking you to confirm each address is intended - only * and 0.0.0.0 produce a WARN.

What each result means

Result thresholds this check applies
ResultWhenWhat it means
PASS`localhost`, `127.0.0.1`, or empty (Unix socket only)Nothing off this host can open a TCP connection to PostgreSQL.
WARN`*` or contains `0.0.0.0`The listener is on every interface, so 5432 answers on any network the host sits on unless a firewall stops it.
PASSAny other specific address (with "confirm each address is intended")A specific address was found and passed, with the message asking you to confirm that address is one you chose.
PASSConfig fallback and no directive (defaults to `localhost`)No directive was set in the file, so PostgreSQL falls back to its own localhost default.
SKIPRoot or sudo requiredNothing was verified. Without root the setting could not be read, so the binding stays unknown.

Why it matters

listen_addresses controls which interfaces accept TCP; pg_hba.conf then controls who may authenticate. Default is localhost. Docker images set *. CIS PostgreSQL "Ensure 'listen_addresses' is set appropriately".

Why it fails, and when it is wrong

  • A managed cluster where the app is remote legitimately listens on a private IP; the check passes those with a note. Only */0.0.0.0 are flagged.
  • :: (IPv6 all) is not in the regex and would be reported as a specific address (PASS). Treat :: like *.
  • Fallback reads only the main postgresql.conf, not conf.d/*.conf or postgresql.auto.conf (where ALTER SYSTEM writes). If a value was set via ALTER SYSTEM, the fallback shows the wrong thing; the PGOK path shows the truth.
  • Multiple clusters (Debian pg_lsclusters) are audited only via the default cluster/socket.

How to fix it

ALTER SYSTEM SET listen_addresses = 'localhost';   -- or '10.0.0.2'

Restart PostgreSQL (this setting needs a restart, not a reload). Pair with hostssl lines scoped to the app subnet.

Verify the fix

sudo -u postgres psql -X -tAc 'SHOW listen_addresses'
# expected: localhost (or a deliberate, specific private address)

ss -tlnp | grep :5432
# expected: 127.0.0.1:5432 only

# Which file supplied the value, on a multi-cluster host:
sudo -u postgres psql -X -c "SELECT name, setting, sourcefile, sourceline FROM pg_settings WHERE name='listen_addresses'"
pg_lsclusters 2>/dev/null      # Debian/Ubuntu: all clusters and their ports

Debugging

The unprivileged path does nothing here. Re-run with sudo.

By design; the check cannot know whether 10.0.0.5 is intended. Confirm it is your private interface and that 5432 is firewalled to the application host, then accept.

listen_addresses needs a restart, not a reload, to take effect. The PGOK path shows the live value; pg_settings.sourcefile shows which file the value came from.

The config fallback takes the first postgresql.conf it finds. On a host with several clusters or versions, use pg_lsclusters and query each cluster on its own port.

Check ss -tlnp | grep 5432 for the truth, and remember a container publishing 5432 bypasses the host setting entirely. Even with a wide listen_addresses, pg_hba.conf still gates access - see PostgreSQL Allowed Hosts.

Sources

How the script reads this

Next

Re-run the Network Isolation audit after applying the fix and confirm this check moves to PASS. CtrlOps runs all 7 checks over your existing SSH connection and scores the result, so the change is visible without reading another config file.

All 7 Network Isolation fixes
Audit your fleet

Find every one of these on every server, in one click

CtrlOps runs this audit over your existing SSH connection - no agents, no scripts to manage. $7/user/month after a 1 month free trial - no credit card required.

Windows

✓ Start instantly·✓ No credit card·✓ No sneaky autorenewals