Postgres connection refused (tested)
connection refused and could not connect to server mean your app reached out for Postgres and found nobody there. We ran a real Postgres in Docker, then killed it, mistyped its password and pointed at a hostname that does not resolve, to capture the exact error for each case.
1. The error you are probably seeing
In Node, the pg driver throws an Error whose code is ECONNREFUSED when a TCP connection to the given host and port is actively rejected. We measured this directly: connecting to port 5434 (nothing listening) with pg 8.23.1 produced this error 3 ms after the connect call:
message: connect ECONNREFUSED 127.0.0.1:5434
code: ECONNREFUSED
errno: -111
syscall: connect
address: 127.0.0.1
port: 5434
The same error, same shape, is what psql's could not connect to server: Connection refused message means per the PostgreSQL docs (not measured here, since this box has no psql binary): the OS rejected the TCP connection outright, almost always because nothing is listening on that host/port.
2. Our test setup
This box has Docker but no Postgres client binaries (which psql pg_isready returned nothing). So we ran a real, throwaway Postgres server and drove it with Node's pg driver instead of psql.
docker run --rm -d --name forever-pgtest -p 5433:5432 \
-e POSTGRES_PASSWORD=test postgres:16-alpine
It logged database system is ready to accept connections 3 seconds after start. We then connected with the correct password, with a wrong password, with a hostname that cannot resolve, and finally stopped the container and reconnected to the same port to capture the exact moment "listening" becomes "refused".
3. Results: four different errors, four different fixes
| Scenario | What we did | Exact error | code | Meaning |
|---|---|---|---|---|
| Nothing listening on the port | Connected to closed port 5434 | connect ECONNREFUSED 127.0.0.1:5434 | ECONNREFUSED | Postgres is not running, or not on this port |
| Server up, correct password | Connected to live postgres:16-alpine on 5433 | connected in 26 ms, PostgreSQL 16.15 returned | – | Happy path, included for comparison |
| Server up, wrong password | Same server, wrong password | password authentication failed for user "postgres" | 28P01 | Server reachable, credentials wrong — not a network problem |
| Hostname does not resolve | Connected to host db (a Docker Compose-style service name), no DNS record for it | getaddrinfo ENOTFOUND db | ENOTFOUND | App is not on the same Docker network as the one defining that hostname |
| Server stopped mid-session | docker stop forever-pgtest, then reconnected to 5433 | connect ECONNREFUSED 127.0.0.1:5433 | ECONNREFUSED | Same shape as case 1 — the driver cannot tell "never started" from "just stopped" |
The important distinction: ECONNREFUSED and 28P01 (password authentication failed) are different errors that look similar in a panicked read of the logs but mean opposite things. ECONNREFUSED means nothing answered the TCP connection at all — the fix is on the network/process side. 28P01 means Postgres answered and rejected your credentials — the fix is in your connection string or pg_hba.conf, and the network is fine.
4. The classic Docker Compose race
If your app and Postgres are both Docker Compose services, a very common sequence is: Compose starts both containers at once, the app container's process reaches its connect() call in milliseconds, but Postgres takes a few seconds to initialize (we measured 3 seconds for postgres:16-alpine to log "ready to accept connections" from a cold image pull plus init). During that window the app gets exactly the ECONNREFUSED we measured in case 1, because the TCP listener genuinely does not exist yet.
This is distinct from a wrong-password failure (case 3), which only happens once Postgres is actually listening. If you see ECONNREFUSED immediately on every container start but it goes away on a manual retry seconds later, that is the startup race, not a configuration bug. Per Docker Compose docs (not measured here), depends_on: condition: service_healthy with a pg_isready healthcheck is the standard fix, since depends_on alone only waits for the container to start, not for Postgres inside it to be ready.
5. Why a hostname/network mismatch produces a different error
We measured that connecting to a hostname with no DNS entry (db, a typical Compose service name) gives ENOTFOUND, not ECONNREFUSED. That distinction matters for diagnosis:
- ENOTFOUND (measured): the name never resolved to an address at all. This happens when your app container is not on the same Docker network as the Postgres container, so the Compose-internal DNS name for the Postgres service does not exist from the app's point of view.
- ECONNREFUSED (measured): the name/address resolved fine, but nothing answered on that port. Per Postgres and Docker docs (not measured here), this is what you get if Postgres is bound to
127.0.0.1only (the container's own loopback) instead of0.0.0.0, or if the app reaches the right IP over a bridge network but the wrong port was published.
If you're debugging across a Docker bridge network vs. host networking, per Docker docs (not measured), containers on the default bridge network reach each other by service name and the container's internal port, not by localhost/127.0.0.1 — localhost inside a container refers to that container itself. Using localhost for the Postgres host from inside another container is a common cause of ECONNREFUSED that looks identical to "Postgres isn't running" but is actually a networking-scope mistake.
6. Common causes, ranked
- Postgres process not started yet or crashed. Measured: gives ECONNREFUSED, identical whether it never started or was stopped after running (see case 1 vs. case 5 above).
- Wrong port in the connection string. Per docs, not measured: produces the same ECONNREFUSED if nothing else is listening on the port you guessed, or a protocol error if something non-Postgres is listening there.
- Docker Compose startup race. Measured timing: our container took 3 s to become ready; an app that connects at container start with no retry/backoff will hit ECONNREFUSED in that window.
- App and database on different Docker networks, or using
localhostacross containers. Measured: gives ENOTFOUND for an unresolvable Compose service name; per docs, gives ECONNREFUSED for a resolvable address with nothing listening (e.g.localhostmisuse). - Postgres bound to 127.0.0.1 only. Per Postgres docs (
listen_addresses), not measured here: a server listening only on loopback refuses connections that arrive from any other interface, including other containers or remote hosts, even though the process is running. - Firewall dropping the port. Per general networking knowledge, not measured: a dropped (not rejected) packet usually times out rather than refusing instantly — a multi-second hang before failure points here rather than at ECONNREFUSED, since our refusals were consistently sub-20 ms.
7. How to fix it, in order
- Confirm the error code first. ECONNREFUSED and ENOTFOUND are network/DNS problems; 28P01 or similar auth codes are not — don't restart Postgres for a password typo.
- Check the process is actually up:
docker psfor a Postgres container, ordocker logs <container>for "ready to accept connections" (what we waited for in our test). - In Docker Compose, add a healthcheck and
depends_on: condition: service_healthyso the app waits for Postgres to be ready, not just started. Add connection retry with backoff in the app regardless — it's cheap insurance against the exact race we measured. - Match host and port exactly. Inside Docker, use the service name from
docker-compose.ymlas the host, neverlocalhost, unless you're using host networking. - If you get ENOTFOUND, check both containers are on the same Compose network (the default network Compose creates, or one you declared explicitly).
- If you get ECONNREFUSED only from outside the Postgres container, check
listen_addressesinpostgresql.confand that the port is actually published (-pindocker run, orports:in Compose).
If the DNS name itself is the problem rather than the port, see DNS lookup. For the general Node.js version of this error outside a database context, see ERR_CONNECTION_REFUSED / ECONNREFUSED. If your client gives up before any of this resolves, see 408 Request Timeout.
Frequently asked questions
What is the exact error code for a Postgres connection refused in Node?
With the pg npm driver (measured, v8.23.1) the thrown error has code ECONNREFUSED and a message like "connect ECONNREFUSED 127.0.0.1:5433". We reproduced this against both a closed port and a Postgres container we stopped mid-test.
Is "connection refused" the same as "password authentication failed"?
No. We measured both: ECONNREFUSED means nothing answered the TCP connection at all. "password authentication failed for user..." (code 28P01) means Postgres answered and rejected your credentials. The first is a network/process problem; the second is a config/credentials problem.
Why does my app get connection refused only when started with Docker Compose?
Most likely the startup race: Compose starts the app and Postgres containers together, but Postgres takes time to initialize. We measured 3 seconds for a fresh postgres:16-alpine container to log ready. An app that connects immediately with no retry hits ECONNREFUSED in that window. Per Docker docs, a healthcheck plus depends_on: condition: service_healthy fixes this.
I get ENOTFOUND instead of ECONNREFUSED — what does that mean?
We measured ENOTFOUND (getaddrinfo ENOTFOUND) when connecting to a hostname that has no DNS record at all, such as a Docker Compose service name used from a container not on that Compose network. ECONNREFUSED means the name resolved but nothing was listening; ENOTFOUND means the name never resolved.
Could Postgres be running but still refuse connections?
Yes, per the PostgreSQL docs (not measured on this box): if listen_addresses is set to only 127.0.0.1, Postgres refuses connections arriving from any other network interface even while the process runs and logs normally. We did measure that a running, reachable Postgres with the wrong password gives a completely different error (28P01), confirming reachability and auth are checked separately.
Written with AI assistance. Measured on 2026-10-02 with Node.js 24.21.0 and the `pg` npm driver v8.23.1, against a real postgres:16-alpine container (PostgreSQL 16.15) run via Docker on 127.0.0.1:5433, and against a definitely-closed local port 5434. This box has no psql or pg_isready binaries, only Docker. Claims about nginx, multi-OS socket paths or application-level retry logic are labelled as such and were not measured. PlainHeaders is our extension; Node.js, the pg driver and Docker are free.