PostgreSQL "Connection refused" is the client telling you that nothing accepted its connection on that address and port. It is not an authentication or permission error: PostgreSQL never saw your request. The good news is that the list of causes is short, and four commands are usually enough to tell which one you have.
The full message comes from libpq, so psql, most drivers and many tools print the same text. On current versions it looks like this:
$ psql -h 10.0.1.20 -U app appdb
psql: error: connection to server at "10.0.1.20", port 5432 failed: Connection refused
Is the server running on that host and accepting TCP/IP connections?
$ psql appdb
psql: error: connection to server on socket "/var/run/postgresql/.s.PGSQL.5432" failed: No such file or directory
Is the server running locally and accepting connections on that socket?
The second line is a hint, not a diagnosis. The libpq source prints "Is the server running on that host and accepting TCP/IP connections?" for any TCP failure of this kind, and the socket variant for local connections. If you do reach the server and it rejects you, you get a different error, such as the ones in our pg_hba.conf entry guide.
Refused, timed out, or rejected: three different problems
- Connection refused: the host answered, but nothing is listening on that port, or a firewall actively rejects the packet. The server is down, listening elsewhere, or on another port.
- Timeout: no answer at all. Usually a firewall or security group silently dropping traffic, a wrong IP or a routing problem.
FATAL: the database system is starting up(orshutting down,in recovery mode,not yet accepting connections): you did reach PostgreSQL, but it is not ready. This is where large databases spend most of their "downtime" after a crash, see below.
Check it in four commands
# 1. does the server answer at all? (0 ok, 1 rejecting, 2 no response)
pg_isready -h 10.0.1.20 -p 5432; echo $?
# 2. on the database host: is anything listening, and where?
ss -ltnp | grep 5432
ss -lx | grep PGSQL
# 3. is the service running, and what did it log?
systemctl status postgresql
journalctl -u postgresql --since "30 min ago"
# 4. what does the server think it should listen on?
psql -c "SHOW listen_addresses" -c "SHOW port" -c "SHOW unix_socket_directories"
pg_isready is the fastest first step: it returns 0 when the server accepts connections, 1 when it is rejecting them (for example during startup), 2 when there is no response, and 3 when no attempt was made because of bad parameters. Exit code 1 and exit code 2 lead to completely different investigations.
The usual causes
- PostgreSQL is not running. It crashed, was stopped, or failed to start. The service status and the server log tell you which. A full disk is a common reason it will not start, see PostgreSQL disk full: pg_wal. Other frequent ones are a typo in
postgresql.conf, a stalepostmaster.pidafter an unclean shutdown, or a data directory that is missing because a volume did not mount. - It listens only on localhost. The default
listen_addressesislocalhost, which allows only loopback TCP connections. Remote clients get "Connection refused" until you add the server's address or*. This parameter is only read at server start, so a reload does nothing. - Wrong port. The default is 5432, but a second cluster on the same host (common on Debian and Ubuntu after an upgrade) often runs on 5433.
ss -ltnpshows what is actually bound. - Wrong socket directory. For local connections the client and server must agree on
unix_socket_directories. The documentation says the default is normally/tmpbut can be changed at build time, and Debian-family packages use/var/run/postgresql. A client built for one layout and a server built for another produce "No such file or directory" even though PostgreSQL is running. Use-h /path/to/socket/dirto point the client at the right place. - A firewall in between. Host firewall, cloud security group, Kubernetes network policy. A rejecting rule looks like "refused", a dropping rule looks like a timeout.
# postgresql.conf (restart required, reload is not enough)
listen_addresses = 'localhost,10.0.1.20' # default is 'localhost'
port = 5432
listen_addresses is only half the change. Once the server listens on a public or private interface, pg_hba.conf decides who may connect. Add narrow rules for the application subnets, keep scram-sha-256, and never pair listen_addresses = '*' with a wide-open firewall.A subtle one: the address was not there at boot
The documentation notes that if listen_addresses is not empty, the server starts as long as it can listen on at least one address, and only emits a warning for the ones it cannot open. If PostgreSQL starts before a virtual IP, a secondary interface or a cloud network attachment is ready, it may come up on localhost only and refuse every remote client, while systemctl status shows it as running. Look for that warning in the startup log, and make the service wait for the network it needs.
What changes on large databases: crash recovery takes time
On a busy multi-terabyte database, the most common reason clients cannot connect after an incident is not a configuration mistake. The server is replaying WAL. After a crash, PostgreSQL must redo everything written since the last checkpoint before it accepts connections. During that time clients see the database system is starting up, or "Connection refused" if the postmaster is not up yet, for example while a supervisor is still restarting it.
LOG: database system was interrupted; last known up at ...
LOG: redo starts at 3A/1C000028
LOG: redo in progress, elapsed time: 10.01 s, current LSN: 3A/5F2D1B40
LOG: redo in progress, elapsed time: 20.02 s, current LSN: 3A/A1E09C88
...
LOG: database system is ready to accept connections
- Watch progress, do not restart. Since PostgreSQL 15,
log_startup_progress_interval(10 s by default) prints "redo in progress" lines with the current LSN. If the LSN moves, recovery is working. Killing and restarting it usually means redoing the same WAL again from the last checkpoint. - Recovery time follows checkpoint settings. The WAL documentation explains that reducing
checkpoint_timeoutandmax_wal_sizemakes checkpoints more frequent and after-crash recovery faster, at the cost of more page flushing. Servers tuned with a very largemax_wal_sizefor write throughput can need much longer to recover. Know that number before you need it. - Check your supervisors. Health checks, Kubernetes liveness probes, cluster managers and service timeouts that expect a database to start in seconds may kill a recovery that needs many minutes, which turns a long start into an endless loop. Give them timeouts that match your real recovery time.
- Standbys have their own states. A replica that has not reached a consistent state answers
the database system is not yet accepting connectionswith the detail "Consistent recovery state has not been yet reached." Read-only clients pointed at it will fail until it gets there. - Prefer failover to waiting. If you have a healthy standby, promoting it is often faster than waiting for a large primary to finish crash recovery. Plan that decision in advance, with fencing so the old primary does not come back as a second writer.
Key takeaways
- "Connection refused" means nothing accepted the connection; it is never a password or pg_hba problem.
- Start with
pg_isready,ss -ltnpand the server log; exit code 1 and 2 mean different things. - Most fixes are
listen_addresses(restart required), the right port or socket directory, or a firewall rule. - After a crash, a large database may be replaying WAL; follow the "redo in progress" lines instead of restarting.
- Size supervisor timeouts to real recovery time and decide in advance when to fail over instead of waiting.