PostgreSQL "connection refused": what it means and how to fix it
"Connection refused" is a TCP-level rejection: nothing accepted the connection at that address and port. That single fact rules out half the usual suspects.
What "connection refused" actually means
"Connection refused" happens below PostgreSQL's authentication and permission layers: your client sent a TCP connection attempt and got an active rejection — either no process is listening on that address and port, or a firewall actively rejected the packet. PostgreSQL never saw the connection. That precision matters, because it immediately excludes passwords, pg_hba.conf rules, and database permissions as causes: those all produce different errors after a connection is established.
Compare the neighboring errors: "no pg_hba.conf entry for host …" means the server is reachable but rejected your client's address or auth method; "password authentication failed" means credentials; "FATAL: sorry, too many clients already" means max_connections is exhausted. A connection that hangs and eventually times out (rather than being refused) points to a firewall silently dropping packets or a routing problem. Each message sends you to a different place — read it exactly.
Common root causes of connection refused
PostgreSQL isn't running
The server crashed, failed to start after a config change, was OOM-killed, or the service simply isn't enabled after a reboot. A failed startup from a typo in postgresql.conf or a full disk is the classic case — the logs name the exact line that broke.
listen_addresses or port mismatch
By default PostgreSQL listens only on localhost. A client connecting from another machine gets refused because the server never bound to that interface — fix with listen_addresses = '*' (or specific IPs) plus a matching pg_hba.conf entry. A non-default port or two clusters on one host cause the same symptom.
Firewall or security group rejecting port 5432
iptables/nftables REJECT rules, cloud security groups, or a managed database's allowlist that doesn't include your client's IP. A REJECT rule produces "connection refused"; a DROP rule produces a hang-then-timeout — the difference tells you which kind of rule you're fighting.
Wrong host: DNS, containers, and localhost confusion
The client points at a stale DNS name, an old IP after a migration — or, inside a container, at localhost, which is the container itself, not the database host. In Docker Compose the hostname is the service name; from a container to the host machine it's host.docker.internal or the host's address.
How to investigate and fix it
Confirm the exact error text, then walk the path from server process to listening socket to network reachability — in that order, because each step's result determines the next.
- 1
Read the exact error message
"Connection refused" → nothing listening or firewall REJECT. "Timed out" → firewall DROP or routing. "No pg_hba.conf entry" → reachable, auth config. "Too many clients" → reachable, pool exhaustion. Skipping this step is how engineers spend an hour in pg_hba.conf for a server that isn't running.
- 2
Verify the server is running
On the database host: systemctl status postgresql (or docker ps / kubectl get pods for containerized setups), then read the PostgreSQL log for startup errors — bad config line, full disk, failed recovery. If it isn't running, the log's last lines explain why; fix that and the refusal disappears.
- 3
Check what it's listening on
Run ss -lntp | grep 5432 (or netstat). Listening on 127.0.0.1:5432 only? Remote clients will be refused — set listen_addresses in postgresql.conf and restart (it is not reloadable). Listening on a different port? Align the client. Nothing listed? The process isn't actually up.
- 4
Test reachability from the client host
From the machine that gets the error: psql -h <host> -p 5432, or nc -zv <host> 5432. Refused from the client but fine locally on the server isolates the problem to the network path or the bind address. Also confirm the hostname resolves to the IP you expect.
- 5
Check firewalls and security groups
Review iptables/nftables rules on the server, cloud security groups, and any managed-database IP allowlist. Add the client's address (or subnet) to the allow rules for port 5432. For managed services, remember outbound NAT — the database sees your egress IP, not your machine's private one.
- 6
In containers, verify the network path
Confirm the right hostname for the topology (service name in Compose, service DNS in Kubernetes, host.docker.internal for container-to-host), the port mapping actually publishes 5432, and both containers share a network. docker exec into the client container and test with nc to see what it sees.
How to prevent connection-refused incidents
- Enable and monitor the PostgreSQL service at the OS level so a reboot or crash never leaves it silently stopped.
- Validate config changes with a reload/restart in staging first — one bad line in postgresql.conf can keep the server from starting.
- Monitor the database host's disk, memory, and the port itself — a full data disk is a leading cause of sudden PostgreSQL exits.
- Manage firewall rules and allowlists as code so a client IP change is a reviewed diff, not a 2 a.m. surprise.
- Use connection pooling and retries with backoff in applications so brief restarts don't cascade into application-wide failures.
How AllStak helps with database connectivity
AllStak's infrastructure monitoring watches the database host itself — CPU, memory, disk, and running services — so the full disk or OOM kill that took PostgreSQL down is on a chart with a timestamp. Your application's connection errors land in error tracking at the same moment, grouped into one issue instead of a flood of duplicates.
With PostgreSQL's logs and your application's logs in the same platform, the "could not connect" line from the app sits next to the database's last startup error on one timeline — which is usually all the correlation a refused connection needs. Uptime checks can also watch a TCP port from outside, confirming recovery independently of the app.
PostgreSQL connection refused — frequently asked questions
What's the difference between "connection refused" and "connection timed out"?
Refused is an active rejection — your packet reached a machine that answered "nothing here": no listener on that port, or a firewall REJECT rule. Timed out means packets disappeared into silence: a firewall DROP rule, a routing problem, or an unreachable host. Refused → check the server and bind address; timed out → check the network path.
Is this a pg_hba.conf problem?
No. pg_hba.conf is evaluated after a TCP connection is established; its rejections produce the explicit error "no pg_hba.conf entry for host …". If you're seeing "connection refused", PostgreSQL never received the connection — look at the process, listen_addresses, the port, and firewalls instead.
Could it be max_connections being exhausted?
No — that produces a different, explicit error: "FATAL: sorry, too many clients already", delivered over a successfully established connection. If you see that one, the fix is connection pooling (pgbouncer or your framework's pool), finding connection leaks, or raising max_connections deliberately. "Refused" means the conversation never started.
Why does it work locally on the server but not from my machine?
Almost certainly listen_addresses. The default is localhost, so connections over the Unix socket or 127.0.0.1 succeed while anything arriving on the network interface is refused. Set listen_addresses appropriately, restart PostgreSQL, add a pg_hba.conf entry for your client's network — and check the firewall next, since it's the same symptom one layer out.
Know when your database host is in trouble
AllStak watches the host's disk, memory, and services, and puts your app's connection errors and the database's logs on one timeline.