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
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.
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:5432With the official MongoDB driver, the same problem comes wrapped in the server selection error:
MongoServerSelectionError: connect ECONNREFUSED 127.0.0.1:27017If 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:5432In 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:
- The database is not running. The service was never started, crashed because it ran out of memory or disk, or its container stopped.
- 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". - 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.
- The database only listens on localhost. The process is up, but it only accepts connections from the same machine.
- 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.
- IPv6 versus IPv4.
localhostresolves to::1, but the database only listens on127.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:
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:
Test-NetConnection 127.0.0.1 -Port 5432If the port is closed, check whether the process exists and which address it listens on. On Linux:
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:
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_addressesdefaults tolocalhost. To accept external connections, change it inpostgresql.confand allow the client inpg_hba.conf. - MongoDB:
mongodlistens on localhost by default. Changenet.bindIpinmongod.conf. - Redis: the default
redis.confships withbind 127.0.0.1 -::1and protected mode on. - MySQL: the
bind_addressdefault is*(all interfaces), but check your configuration file: Ubuntu'smysql-serverpackage, for example, setsbind-address = 127.0.0.1in/etc/mysql/mysql.conf.d/mysqld.cnf.
An example in postgresql.conf:
listen_addresses = '10.0.0.5' # private interface IP, not '*' without a firewallOpening 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:
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_healthyOutside Compose, make your own code retry with a growing delay. With pg:
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
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.
More in Databases
See all 24 articles- How much does database hosting cost?Cloud database hosting costs from zero to hundreds of dollars a month. See what sets the price: instance, disk, traffic, snapshots, and how to estimate yours.
- How to host a database for your appTo host a database, pick an engine, create the instance, store the connection string in an environment variable and test the connection. Step-by-step guide.
- What is Redis used for?Redis is an in-memory data store that keeps key-value pairs and rich data structures. Learn how it works, what it is used for and when not to use it.
- How to back up your databaseBack up MySQL, PostgreSQL and MongoDB with mysqldump, pg_dump and mongodump, then restore the dump. See the commands, how to verify and common mistakes.