ECONNREFUSED: database connection refused

ECONNREFUSED means the target machine answered, but no process is listening on that host and port. Usually the database is stopped, the address is wrong or the server only accepts local connections.

Updated 7 min read

Learning objectives

After reading this article you will be able to:

  • Recognize the ECONNREFUSED error in Node.js, Python and psql
  • Find the cause with port and address checks
  • Fix a stopped server, a wrong host, a local-only bind and startup order
On this page

You start the app, it tries to open the database connection and stops right away with ECONNREFUSED. It is one of the most common errors for anyone starting out with PostgreSQL, MySQL, MongoDB or Redis, and it shows up both in the first local setup and in the first deploy.

The good news is that the error is specific. It says nothing about passwords, permissions or queries: it says that, at the address and port your code used, no program accepted the connection. Your job is to find out why.

ECONNREFUSEDnothing listeningTest the portnc orTest-NetConnectionclosedIs the database running?no: start it, read its log1Is the host right?no: Compose service nameor the provider's host2Is it the right port?no: fix it in the variable3Bound to localhost only?yes: change the bind, restart4
ECONNREFUSED means nothing listens on that host and port: test the port, then go through the checks in order.

What does the error look like?#

In Node.js, with the pg, mysql2 and redis drivers, the message shows the IP and port that were tried:

Error: connect ECONNREFUSED 127.0.0.1:5432

With the official MongoDB driver, the same problem comes wrapped in the server selection error:

MongoServerSelectionError: connect ECONNREFUSED 127.0.0.1:27017

If the host is localhost, recent Node.js versions try both IPv6 and IPv4. When both fail, the error becomes an AggregateError with the code ECONNREFUSED, and each attempt sits inside it:

AggregateError [ECONNREFUSED]:
  connect ECONNREFUSED ::1:5432
  connect ECONNREFUSED 127.0.0.1:5432

In PostgreSQL, psql shows the text below, and the Python drivers built on libpq (such as psycopg) repeat the same message inside an OperationalError:

psql: error: connection to server at "127.0.0.1", port 5432 failed: Connection refused
        Is the server running on that host and accepting TCP/IP connections?

The Node.js documentation defines the code this way: no connection could be made because the target machine actively refused it, which usually comes from trying to reach a service that is inactive on the host. The PostgreSQL documentation adds: "Connection refused" does not mean the server got your request and rejected it; it means no server was listening there.

How is ECONNREFUSED different from other connection errors?#

Identifying the right code saves time, because each one points to a different layer:

Error What happened Where to look
ECONNREFUSED The host answered, but the port is closed Database stopped, wrong port, local bind
ETIMEDOUT No answer in time Firewall, network, unreachable IP
ENOTFOUND The host name did not resolve in DNS Misspelled or nonexistent host name
password authentication failed / Access denied Connected, but the credentials failed User and password
too many connections Connected, but the connection limit ran out Pool and open connections

If yours is the last one, the path is different: see too many connections.

What are the most common causes?#

From most to least frequent:

  1. The database is not running. The service was never started, crashed because it ran out of memory or disk, or its container stopped.
  2. The host is wrong. The code uses localhost, but the database is on another machine or in another container. This is the number one cause of "works locally, breaks in production".
  3. The port is wrong. The default ports are 5432 (PostgreSQL), 3306 (MySQL), 27017 (MongoDB) and 6379 (Redis). A database set up on another port refuses the default one.
  4. The database only listens on localhost. The process is up, but it only accepts connections from the same machine.
  5. The app started before the database. In Docker Compose, or on a server that just rebooted, the app tries to connect while the database is still starting.
  6. IPv6 versus IPv4. localhost resolves to ::1, but the database only listens on 127.0.0.1 (or the other way around).

How do I find the cause?#

First, confirm whether the port is open, without involving your code. On Linux and macOS, nc tests any port, and each database has its own check:

bash
nc -vz 127.0.0.1 5432
pg_isready -h 127.0.0.1 -p 5432
redis-cli -h 127.0.0.1 -p 6379 ping
mongosh "mongodb://127.0.0.1:27017" --eval "db.runCommand({ ping: 1 })"

On Windows, use PowerShell:

powershell
Test-NetConnection 127.0.0.1 -Port 5432

If the port is closed, check whether the process exists and which address it listens on. On Linux:

bash
sudo ss -ltnp | grep -E '5432|3306|27017|6379'

The local address column shows the bind: 127.0.0.1:5432 only accepts local connections; 0.0.0.0:5432 or *:5432 accepts them on any interface. If nothing shows up, the database is down: check its log (journalctl -u postgresql, docker logs <container>) to see why it stopped.

Finally, print what your code is actually using. An empty or stale environment variable explains many cases:

bash
node -e "console.log(new URL(process.env.DATABASE_URL).host)"

How do I fix each cause?#

Database stopped. Start the service (sudo systemctl start postgresql, docker start <container>) and find out why it stopped. If it ran out of memory or disk, it will go down again until you fix that.

Wrong host in production or in a container. Inside a container, localhost is the container itself, not your machine. In Docker Compose, use the service name as the host (db, for example). In production, use the host your database provider gives you, always through an environment variable, never hard-coded.

Bound to localhost only. Each database has its own setting, and all of them need a service restart:

  • PostgreSQL: listen_addresses defaults to localhost. To accept external connections, change it in postgresql.conf and allow the client in pg_hba.conf.
  • MongoDB: mongod listens on localhost by default. Change net.bindIp in mongod.conf.
  • Redis: the default redis.conf ships with bind 127.0.0.1 -::1 and protected mode on.
  • MySQL: the bind_address default is * (all interfaces), but check your configuration file: Ubuntu's mysql-server package, for example, sets bind-address = 127.0.0.1 in /etc/mysql/mysql.conf.d/mysqld.cnf.

An example in postgresql.conf:

listen_addresses = '10.0.0.5'   # private interface IP, not '*' without a firewall

Opening the database to the internet without a firewall, a strong password and TLS is a serious risk. Prefer listening on the private network only and allowing just your application's IPs.

App before the database. In Docker Compose, a plain depends_on only waits for the container to start, not to be ready. Use a healthcheck:

yaml
services:
  db:
    image: postgres:18
    environment:
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      timeout: 3s
      retries: 10
  app:
    build: .
    environment:
      DATABASE_URL: postgresql://postgres:${POSTGRES_PASSWORD}@db:5432/postgres
    depends_on:
      db:
        condition: service_healthy

Outside Compose, make your own code retry with a growing delay. With pg:

js
import pg from "pg";

const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

async function connectWithRetry(tries = 5) {
  for (let i = 1; ; i++) {
    const client = new pg.Client({ connectionString: process.env.DATABASE_URL });
    try {
      await client.connect();
      return client;
    } catch (err) {
      if (i >= tries || err.code !== "ECONNREFUSED") throw err;
      const wait = 1000 * 2 ** (i - 1); // 1 s, 2 s, 4 s, 8 s
      console.error(`Database unavailable (attempt ${i}/${tries}), retrying in ${wait} ms`);
      await sleep(wait);
    }
  }
}

const db = await connectWithRetry();
const { rows } = await db.query("select now()");
console.log(rows[0]);
await db.end();

The code only retries on ECONNREFUSED: a password error or a nonexistent host does not get better by waiting. A pg.Client that failed cannot be reused, so the loop creates a new one on each attempt.

IPv6 versus IPv4. Replace localhost with 127.0.0.1 in the connection string, or make the database listen on both addresses.

How do I keep it from coming back?#

  • Keep host, port, user and password in a single connection variable, set in each environment. See what a .env file is.
  • Make the app fail with a clear message if the variable is missing, instead of falling back to localhost.
  • Add a healthcheck for the database and automatic restarts for both the database and the app.
  • Watch memory and disk on the database server: running out of either is the most common cause of a silent crash.
  • If you would rather not deal with bind addresses, firewalls and restarts yourself, consider database hosting separate from your own machine.

How to do this on Square Cloud#

Square Cloud offers MongoDB, PostgreSQL, MySQL and Redis hosting starting on the Standard plan. You create the database in the dashboard, and it is provisioned in seconds in the same datacenter as your applications, with an average latency of about 1 ms. The Hobby plan does not include databases.

The database does not run inside your application's container: it has its own host and port, shown in the dashboard along with the full connection URL. That is why the most common mistake when migrating is an app that still points to localhost or to the engine's default port. Keep the dashboard URL in an application environment variable, set in the dashboard, the CLI or the API; the change takes effect on the next restart, with no rebuild.

TLS is mandatory: download the certificate in the dashboard and point the driver at the CA (on PostgreSQL and MongoDB the client also presents the .pem certificate; on Redis, use rediss://). Because the database accepts outside connections, you can run the port test and the engine's client from your own machine, with the same host, port and certificate, to tell a network problem from a code problem.

You take manual snapshots of the database whenever you want, and every plan that includes databases also has an automatic daily snapshot. See the details on database hosting.

Database hosting

MongoDB, PostgreSQL, MySQL and Redis next to your app

Learn more

FAQs

Is ECONNREFUSED a password error?

No. The refusal happens before any authentication: nothing is listening on that port. A wrong password produces a different message, such as password authentication failed in PostgreSQL or Access denied in MySQL.

Why does ::1 show up in the error when I set localhost?

Because localhost can resolve to the IPv6 address ::1 and to the IPv4 address 127.0.0.1. Node.js tries both and, if both fail, groups the failures into an AggregateError. If the database only listens on 127.0.0.1, use that IP instead of localhost.

What is the difference between ECONNREFUSED and ETIMEDOUT?

ECONNREFUSED is an immediate refusal: the machine exists, but the port is closed. ETIMEDOUT is a lack of response, common when a firewall drops the packets or the host is unreachable.

It works on my computer and fails in production. Why?

Almost always the connection variable in production still points to localhost, or it was never set and the code fell back to a default. In production the database lives on another host, which must be in the connection string.

Next articleBest database for Node.jsFor most Node.js projects, PostgreSQL is the safest choice. See when to use MySQL, MongoDB, Redis or SQLite instead, with drivers and code examples.

More in Databases

See all 24 articles