listen_addresses decides which TCP sockets exist. It has nothing to say about who may use them; that is pg_hba.conf’s job, and most of the grief this parameter causes comes from confusing the two.

It is a string, a comma-separated list of host names and IP addresses, and the default is localhost. Context is postmaster, so changing it means a restart. You cannot open a new interface on a running server; ALTER SYSTEM accepts the change, pg_settings.pending_restart goes true, and nothing else happens until you bounce it. Two spellings are special: * means every interface the host has, IPv4 and IPv6, and the empty string means no TCP sockets at all, leaving only Unix-domain sockets. 0.0.0.0 and :: are the single-family wildcards.

It arrived in PostgreSQL 8.0, replacing tcpip_socket, virtual_host (one address, no more), and the -i switch everyone forgot. localhost was the compromise default: nothing on the network can reach the server, but clients that cannot speak Unix sockets (Windows, the JDBC driver) work out of the box. It is still the right default for a fresh initdb and the wrong setting for nearly every server anyone deploys.

What the postmaster does with it

At startup the postmaster walks the list in order. Each entry goes through getaddrinfo(), which can return several addresses for one name (on a host with IPv6, localhost is both 127.0.0.1 and ::1), and a socket is bound to each. * is the null-host passive lookup, yielding 0.0.0.0 and ::; IPv6 sockets get IPV6_V6ONLY so the two wildcards can share a port. Resolution happens once, at startup. A later DNS change is invisible until the next restart, which is one reason to prefer literal addresses.

Failures are per-entry and non-fatal. An address that cannot be bound gets a LOG line with the errno and a WARNING: could not create listen socket for "...", and the server carries on with whatever it did bind. Only when every entry fails does it refuse to start, with FATAL: could not create any TCP/IP sockets. This is more forgiving than you want it to be, because the list is not a union. 127.0.0.1,* binds the loopback, then fails to bind 0.0.0.0 with “Address already in use,” warns, and comes up listening on loopback only. On a dual-stack host it is slightly worse: :: binds fine, so IPv6 clients get in while IPv4 clients on the network get connection refused, and the config file says * right there. This is exactly what happens when someone adds * after an existing localhost entry instead of replacing it.

SHOW listen_addresses reports what the configuration says. The startup log reports what happened, one listening on IPv4 address "..." line per socket that bound and a could not bind line for each that did not; ss -ltnp says the same thing from the kernel’s side. When they disagree, the log wins.

Reading the client’s error

The most useful thing this parameter does is show up in error messages by its absence. “Connection refused” means nothing is listening at that address and port: wrong listen_addresses, wrong port, or no server. A silent timeout usually means a firewall or security group ate the SYN. “no pg_hba.conf entry for host …” means you are past this parameter entirely; the socket exists, the connection was accepted, and pg_hba.conf turned it away. Three error shapes, three places to look, and listen_addresses is only responsible for the first.

The converse is quieter. With an empty listen_addresses, every host line in pg_hba.conf is dead code, and nothing warns you.

Where it earns its keep

The docs pitch it as a way to keep connection attempts off untrusted interfaces, which is true as far as it goes, but it selects interfaces, not clients. On a single-interface host behind a firewall or security group, with a pg_hba.conf that names real CIDRs, * is correct and there is no security argument against it. The official Docker image ships with *; RDS and Aurora do not expose the parameter at all.

It matters in two situations. The first is a multi-homed host: a public interface and a private one, or a dedicated replication network. Bind the private address explicitly and the public interface never has a PostgreSQL socket on it, which beats trusting a firewall rule to stay put. The second is the single-box deployment where the application, or a local PgBouncer, is the only client. Set it to the empty string and there is no TCP socket for anyone to find; pg_hba.conf shrinks to its local lines, access control becomes a filesystem permission on the socket directory, and bonjour has nothing to advertise.

One caution for the explicit-address case. A socket can only be bound to an address the kernel currently holds, so a floating VIP cannot be listed on a node that does not own it yet: the bind fails with “Cannot assign requested address,” the server starts anyway if anything else bound, and failover hands the VIP to a server with no socket behind it. On HA nodes, bind * or the interface’s fixed address and let the VIP route to it. (Linux has net.ipv4.ip_nonlocal_bind. I would not build failover on it.)

So: * behind a real firewall and an honest pg_hba.conf for almost everyone; a specific private address when the machine has a public face; the empty string when nothing needs TCP. Decide before the server starts, because this is one of the parameters you cannot fix while the new monitoring host is waiting to connect.

Related