listen_addresses in PostgreSQL: the parameter that opens sockets, not ports
A seemingly trivial GUC concentrates much of the connection incidents in production. Understanding that it controls sockets, not access, avoids nearly all the pain.

An article from the "All Your GUCs in a Row" series, published on Planet PostgreSQL, dedicates a chapter to a parameter that seems trivial but concentrates much of the connection incidents in production: listen_addresses. The central thesis of the text is direct and worth repeating before anything else: listen_addresses decides which TCP sockets exist, and nothing else. Who can use those sockets is the business of pg_hba.conf. Confusing the two is the root of most of the pain this parameter causes.
What it is and why it requires a restart
The value is a string, a comma-separated list of host names and IP addresses. The default on a fresh install is localhost. The operational detail that trips up a lot of people is the context: postmaster. This means changing listen_addresses requires restarting the server. There is no way to open a new interface to the database while it's running.
The point deserves attention because the behavior is silent and misleading. An ALTER SYSTEM SET listen_addresses = '...' is accepted without error, pg_settings.pending_restart turns to true, and nothing else happens. The new socket is only born on the next restart. The text notes that this is exactly the kind of parameter that can't be fixed in the heat of the moment, with the new monitoring host waiting to connect. The decision needs to be made before the server comes up.
Two spellings have special meaning:
*opens a socket on every interface the host has, IPv4 and IPv6.- The empty string means no TCP socket at all: only Unix-domain sockets remain.
0.0.0.0and::are the single-family wildcards (IPv4-only and IPv6-only, respectively).
The parameter arrived in PostgreSQL 8.0, replacing tcpip_socket, virtual_host, and the command-line -i switch. localhost was chosen as the middle-ground default: nothing from the network reaches the server, but clients that don't speak Unix socket (Windows, the JDBC driver) work right away. It's the right default for a freshly created initdb and the wrong one for practically any server put into production.
How the postmaster processes the list
At startup, the postmaster walks the list in order. Each entry goes through getaddrinfo(), which can return multiple addresses for the same name (on a host with IPv6, localhost resolves to both 127.0.0.1 and ::1), and a socket is bound to each one. * produces 0.0.0.0 and ::; the IPv6 sockets get IPV6_V6ONLY so that the two wildcards can share the same port.
A critical point: name resolution happens exactly once, at startup. A later DNS change stays invisible until the next restart. It's one of the reasons the source recommends preferring literal addresses over names.
The failure handling is what makes this parameter treacherous. Failures are per-entry and not fatal. An address that fails to bind produces a LOG line with the errno and a WARNING: could not create listen socket for "...", and the server proceeds with whatever it managed to bind. Only when all entries fail does it refuse to start, with FATAL: could not create any TCP/IP sockets.
This is more permissive than one would like, because the list is not a union. The example given in the text is precise: 127.0.0.1,* binds the loopback, then fails to bind 0.0.0.0 with "Address already in use", emits the warning, and comes up listening only on loopback. On a dual-stack host the effect is even worse: :: binds without a problem, so IPv6 clients get in while IPv4 clients from the network get connection refused, and the config file says * right there. It's exactly what happens when someone adds * to an existing localhost entry instead of replacing it.
Diagnosis: what the log says versus what the config says
There is an important divergence between sources of truth. 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 one error line for each that didn't. ss -ltnp tells the same story from the kernel's side. When they disagree, the log wins.
On the client side, the three error formats point to different places:
- "Connection refused": nothing is listening on that address and port. Suspects: wrong
listen_addresses, wrongport, or the server being down. - Silent timeout: usually a firewall or security group swallowed the SYN.
- "no pg_hba.conf entry for host ...": here
listen_addresseshas already done its job. The socket exists, the connection was accepted, and it waspg_hba.confthat rejected it.
There's also a silent and dangerous case: with listen_addresses empty, every host line in pg_hba.conf becomes dead code, and nothing warns you about it.
Where it's actually worth it
The documentation sells the parameter as a way to keep connection attempts off untrusted interfaces. That's true up to a point, but the text makes a distinction the DBA needs to internalize: 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's no security argument against it. The official Docker image already ships with *, and RDS and Aurora don't even expose the parameter.
Two situations actually justify explicit configuration:
- Multi-homed host: a public interface and a private one, or a dedicated replication network. Explicitly binding the private address guarantees the public interface never has a PostgreSQL socket on it, which is more robust than trusting that a firewall rule will stay in place.
- Single-box deployment where the application, or a local PgBouncer, is the only client. With the empty string, there's no TCP socket for anyone to find:
pg_hba.confshrinks down to its local lines and access control becomes a filesystem permission on the socket directory.
The caveat with VIPs in high availability
The text leaves a specific warning for the case of an explicit address in HA environments. A socket can only be bound to an address the kernel currently owns. A floating VIP, therefore, cannot be listed on a node that isn't yet its owner: the bind fails with "Cannot assign requested address", the server comes up anyway if something else bound, and failover hands the VIP to a server with no socket behind it.
The recommendation on HA nodes is to bind * or the interface's fixed address and let the VIP route to it. Linux has net.ipv4.ip_nonlocal_bind, but the source is categorical: "I wouldn't build failover on top of that."
The practical summary for anyone running PostgreSQL in production in Brazil: * behind a real firewall and an honest pg_hba.conf works for almost everyone; a specific private address when the machine has a public face; and the empty string when nothing needs TCP. And, above all, decide before bringing the server up.
Translated from the Brazilian Portuguese original · Read the original
PostgreSQL RPM Brings AI-Powered pg_statviz to RHEL, SLES, and Amazon Linux
Devrim Gündüz, maintainer of the PGDG RPM repositories, detailed how pg_statviz began offering analysis via Claude, GPT, Gemini, and Ollama across six Linux distribution families, and why it took three days of packaging work.