Error: connect ECONNREFUSED 127.0.0.1:PORT means nothing is listening on that host and port — the OS rejected the connection immediately. Start the target service (database, API server, Redis), verify you are using the correct port and host, and if running inside Docker replace localhost with the Compose service name. On Node.js 18+, try 127.0.0.1 instead of localhost to avoid IPv6 resolution.
What is ECONNREFUSED?
ECONNREFUSED stands for Error CONNection REFUSEd — a
POSIX system error
thrown when Node.js sends a TCP SYN packet to a host and port and the
operating system immediately replies with a TCP RST (reset) or an ICMP
port-unreachable message. This happens because no process is bound to that port and
accepting connections.
Unlike ETIMEDOUT (where the packet disappears into the network with no reply) or
ECONNRESET (where an established connection is later closed by the peer), ECONNREFUSED
is an immediate and unambiguous rejection: the connection was never
established at all.
Error: connect ECONNREFUSED 127.0.0.1:3000Error: connect ECONNREFUSED 127.0.0.1:5432Error: connect ECONNREFUSED 127.0.0.1:6379Error: connect ECONNREFUSED 127.0.0.1:27017Error: connect ECONNREFUSED ::1:3000Error: connect ECONNREFUSED 0.0.0.0:8080
Full Error Example
Error: connect ECONNREFUSED 127.0.0.1:5432
at TCPConnectWrap.afterConnect [as oncomplete] (node:net:1494:16)
at TCPConnectWrap.callbackTrampoline (node:internal/async_hooks:130:17) {
errno: -61,
code: 'ECONNREFUSED',
syscall: 'connect',
address: '127.0.0.1',
port: 5432
}
The error object exposes err.address and err.port alongside
err.code. These tell you exactly which host and port Node.js tried to reach,
which is the starting point for diagnosis. The err.syscall is always
'connect' for ECONNREFUSED — the failure happens before any data is exchanged.
Common Causes
| Cause | Why it happens |
|---|---|
| Target service not running (most common) | The database, API server, or cache process was never started, or it has not finished starting up yet. No process is bound to the port so every connection attempt is refused. |
| Wrong port number | The client is connecting to a different port than the one the server is listening on — e.g., connecting to 5432 when PostgreSQL was started on 5433, or a misconfigured PORT environment variable. |
| Wrong host or IP / IPv6 mismatch | The server listens on 127.0.0.1 (IPv4) but the client connects to ::1 (IPv6) because localhost resolved to the IPv6 loopback. Node.js 18+ native fetch prefers IPv6, making this more common. |
| Server bound to wrong interface | The server binds to 127.0.0.1 (loopback only) but is accessed from another host or container that connects via the machine's external IP. The OS refuses because it is not listening on that interface. |
| Service crashed or exited | The target process was running but exited due to an error, OOM kill, or signal. The port is now unbound and new connections are refused. |
| Firewall blocking connection | A local firewall rule (iptables, ufw, Windows Firewall) or cloud security group is dropping or actively rejecting packets on the target port before they reach the listening process. |
| Docker networking | Inside Docker Compose, localhost resolves to the container itself, not to sibling services. A service listening in another container is unreachable via localhost or 127.0.0.1. |
| Proxy or corporate network misconfiguration | An HTTP/HTTPS proxy intercepts the request but the proxy itself refuses the connection, or the HTTP_PROXY/NO_PROXY environment variables route localhost traffic through an unreachable proxy. |
| Missing protocol in URL | Passing a URL without http:// or https:// (e.g., fetch('localhost:3000')) causes Node.js to misparse the host, resulting in a connection to an unexpected address. |
Fix 1 – Verify the target service is running
Before anything else, confirm that a process is actually listening on the expected port. The address and port are shown directly in the error message — use them immediately.
# macOS / Linux — list processes listening on a specific port
lsof -i :3000
lsof -i :5432 # PostgreSQL default
lsof -i :6379 # Redis default
lsof -i :27017 # MongoDB default
# Linux alternative
netstat -tlnp | grep :3000
ss -tlnp | grep :3000
# Check if a service process is running at all
ps aux | grep postgres
ps aux | grep redis-server
ps aux | grep mongod
# Windows
netstat -ano | findstr :3000
Get-Process -Name "node" | Select-Object Id, Name
If the port shows no listener, start the service. For common services:
# Start PostgreSQL
brew services start postgresql@16 # macOS with Homebrew
sudo systemctl start postgresql # Linux systemd
# Start Redis
brew services start redis # macOS
sudo systemctl start redis # Linux
# Start MongoDB
sudo systemctl start mongod # Linux
brew services start mongodb-community # macOS
# Start your Node.js app
npm start
node server.js
docker-compose.yml.
Fix 2 – Check and correct the host/port configuration
Mismatched environment variables are the second most common cause. The error message shows the exact address Node.js used — compare it against where the service is actually bound.
# .env file — verify these match the running service
DB_HOST=127.0.0.1
DB_PORT=5432
REDIS_URL=redis://127.0.0.1:6379
API_URL=http://127.0.0.1:3000
# In Node.js — log resolved values before connecting
console.log('Connecting to', process.env.DB_HOST, ':', process.env.DB_PORT);
const { Pool } = require('pg');
const pool = new Pool({
host: process.env.DB_HOST || '127.0.0.1',
port: Number(process.env.DB_PORT) || 5432,
database: process.env.DB_NAME,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
});
fetch('localhost:3000')
without http:// will fail or connect to an unexpected address.
Always use fetch('http://localhost:3000') or fetch('http://127.0.0.1:3000').
Fix 3 – Resolve localhost vs 127.0.0.1 vs ::1 (IPv6)
On systems with IPv6 enabled, localhost may resolve to ::1
(IPv6) rather than 127.0.0.1 (IPv4). If the server is only bound to
127.0.0.1, connecting via ::1 fails with ECONNREFUSED.
Node.js 18+ native fetch is particularly prone to this because it uses
the Happy Eyeballs algorithm and prefers IPv6.
// Problem: localhost resolves to ::1 but server listens on 127.0.0.1 only
const client = new Client({ host: 'localhost', port: 5432 }); // may hit ::1
// Fix option 1: use explicit IPv4 loopback address
const client = new Client({ host: '127.0.0.1', port: 5432 });
// Fix option 2: start Node.js with IPv4 DNS preference
// node --dns-result-order=ipv4first app.js
// Fix option 3: bind the server to both IPv4 and IPv6 (dual-stack)
const server = net.createServer();
server.listen(3000, '::', () => {
console.log('Listening on IPv4 and IPv6 (dual-stack)');
});
For the Node.js 18+ built-in fetch API specifically:
// Instead of:
const res = await fetch('http://localhost:3000/api/data'); // may resolve to ::1
// Use explicit IPv4:
const res = await fetch('http://127.0.0.1:3000/api/data');
// Or start node with:
// node --dns-result-order=ipv4first server.js
Fix 4 – Fix Docker networking
In Docker Compose, each container has its own network namespace. A service cannot
reach a sibling container via localhost — use the Compose service name
as the hostname instead. Docker's internal DNS resolves service names to the correct
container IP automatically.
# docker-compose.yml
version: "3.9"
services:
app:
build: .
environment:
# WRONG: localhost resolves to the app container itself
# DB_HOST: localhost
# CORRECT: use the Compose service name
DB_HOST: postgres
DB_PORT: 5432
REDIS_URL: redis://redis:6379
MONGO_URL: mongodb://mongo:27017/mydb
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_started
mongo:
condition: service_started
postgres:
image: postgres:16
environment:
POSTGRES_PASSWORD: secret
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 5s
retries: 5
redis:
image: redis:7
mongo:
image: mongo:7
depends_on waits for the
container to start, not for the service inside to be ready. Use
condition: service_healthy combined with a healthcheck
to ensure the database is accepting connections before your app starts.
Alternatively, use a wait-for-it.sh script or add retry logic in your app.
Connecting from the Docker host to a container
If you are running Node.js on the host machine (not inside Docker) and need to
connect to a service running inside Docker, use localhost with the published
port. Do not use the container name — DNS resolution of container names only works
inside Docker networks.
# On the host, connect via the published port:
# (docker run -p 5432:5432 postgres)
const pool = new Pool({ host: '127.0.0.1', port: 5432 });
# From inside Docker Desktop on Mac/Windows, use:
# host.docker.internal instead of localhost to reach the host machine
DB_HOST=host.docker.internal
Fix 5 – Retry with exponential backoff for startup race conditions
When two services start in parallel and the order is not guaranteed, your app may
get ECONNREFUSED during the dependency's startup window. Wrap the initial connection
attempt in a retry loop that catches ECONNREFUSED specifically and backs
off exponentially.
const { Client } = require('pg');
async function connectWithRetry({ host, port, retries = 10, baseDelayMs = 500 } = {}) {
for (let attempt = 1; attempt <= retries; attempt++) {
const client = new Client({ host, port, database: 'mydb', user: 'app', password: 'secret' });
try {
await client.connect();
console.log(`Connected to ${host}:${port} on attempt ${attempt}`);
return client;
} catch (err) {
if (err.code === 'ECONNREFUSED') {
const delay = baseDelayMs * 2 ** (attempt - 1);
console.warn(`ECONNREFUSED – attempt ${attempt}/${retries}, retrying in ${delay}ms`);
await new Promise((resolve) => setTimeout(resolve, delay));
} else {
throw err; // Non-retryable error (auth failure, SSL error, etc.)
}
}
}
throw new Error(`Could not connect to ${host}:${port} after ${retries} attempts`);
}
// Usage
const db = await connectWithRetry({ host: process.env.DB_HOST || '127.0.0.1', port: 5432 });
Fix 6 – Database-specific reconnection configuration
Mongoose (MongoDB)
Mongoose has built-in reconnection support. Configure serverSelectionTimeoutMS
and connectTimeoutMS so it retries internally before throwing.
const mongoose = require('mongoose');
mongoose.connect(process.env.MONGO_URL || 'mongodb://127.0.0.1:27017/mydb', {
serverSelectionTimeoutMS: 10000, // Timeout after 10s if no server found
connectTimeoutMS: 10000,
socketTimeoutMS: 45000,
// Mongoose will automatically retry connections on ECONNREFUSED
});
mongoose.connection.on('error', (err) => {
if (err.code === 'ECONNREFUSED') {
console.error('MongoDB connection refused — is mongod running?');
}
});
mongoose.connection.on('connected', () => {
console.log('MongoDB connected');
});
ioredis (Redis)
ioredis provides a retryStrategy callback that fires on every reconnect
attempt, including ECONNREFUSED. Return a delay in milliseconds to retry, or a non-number
to stop retrying.
const Redis = require('ioredis');
const redis = new Redis({
host: process.env.REDIS_HOST || '127.0.0.1',
port: 6379,
retryStrategy(times) {
if (times > 10) return null; // Stop retrying after 10 attempts
return Math.min(times * 200, 3000); // Exponential backoff, max 3 seconds
},
reconnectOnError(err) {
return err.code === 'ECONNREFUSED';
},
});
redis.on('error', (err) => {
if (err.code === 'ECONNREFUSED') {
console.error('Redis connection refused — is redis-server running?');
}
});
Fix 7 – Health check before connecting
For any TCP service, probe the port before your app marks itself as ready and begins serving traffic. This prevents cascading failures when a downstream dependency is unavailable.
const net = require('net');
/**
* Returns true if a TCP port is open, false on ECONNREFUSED or timeout.
*/
function isPortOpen(host, port, timeoutMs = 1000) {
return new Promise((resolve) => {
const socket = new net.Socket();
const cleanup = (result) => { socket.destroy(); resolve(result); };
socket.setTimeout(timeoutMs);
socket.on('connect', () => cleanup(true));
socket.on('error', (err) => cleanup(err.code !== 'ECONNREFUSED'));
socket.on('timeout', () => cleanup(false));
socket.connect(port, host);
});
}
async function waitForService(host, port, { retries = 20, intervalMs = 1000 } = {}) {
for (let i = 0; i < retries; i++) {
if (await isPortOpen(host, port)) {
console.log(`${host}:${port} is ready`);
return;
}
console.log(`Waiting for ${host}:${port}... (${i + 1}/${retries})`);
await new Promise((resolve) => setTimeout(resolve, intervalMs));
}
throw new Error(`Service ${host}:${port} not available after ${retries} attempts`);
}
// Usage — wait for PostgreSQL before starting the app
await waitForService(process.env.DB_HOST || '127.0.0.1', 5432);
await startApp();
Fix 8 – Axios error handling for ECONNREFUSED
When using Axios, ECONNREFUSED surfaces inside error.code on the caught
error object. Axios wraps the underlying Node.js system error so you can detect and
retry it.
const axios = require('axios');
async function fetchWithRetry(url, { retries = 5, baseDelayMs = 300 } = {}) {
for (let attempt = 1; attempt <= retries; attempt++) {
try {
const response = await axios.get(url, { timeout: 5000 });
return response.data;
} catch (err) {
const isRefused = err.code === 'ECONNREFUSED';
const isLastAttempt = attempt === retries;
if (isRefused && !isLastAttempt) {
const delay = baseDelayMs * 2 ** (attempt - 1);
console.warn(`ECONNREFUSED (attempt ${attempt}/${retries}), retrying in ${delay}ms`);
await new Promise((resolve) => setTimeout(resolve, delay));
} else {
throw err;
}
}
}
}
// Usage
const data = await fetchWithRetry('http://127.0.0.1:3000/api/status');
Fix 9 – Fix proxy misconfiguration
If you are behind a corporate HTTP proxy, the HTTP_PROXY or
HTTPS_PROXY environment variable may route all traffic through a proxy
that cannot reach localhost — causing ECONNREFUSED from the proxy.
Ensure localhost and 127.0.0.1 are in your NO_PROXY
list.
# Shell environment — exclude loopback from proxy routing
export NO_PROXY=localhost,127.0.0.1,::1
# Or in Node.js before requiring http clients:
process.env.NO_PROXY = 'localhost,127.0.0.1,::1';
Fix 10 – Check server interface binding
If your server is only bound to 127.0.0.1 (loopback) but you need to
reach it from another machine, container, or VM, you must bind to all interfaces.
// Express — bind to all interfaces (0.0.0.0)
const app = require('express')();
app.listen(3000, '0.0.0.0', () => {
console.log('Listening on all interfaces on port 3000');
});
// Or for dual-stack IPv4 + IPv6:
app.listen(3000, '::', () => {
console.log('Listening on all IPv4 and IPv6 interfaces');
});
Debugging Checklist
- Read
err.addressanderr.portfrom the error object — they tell you exactly what Node.js tried to connect to. - Run
lsof -i :PORT(macOS/Linux) ornetstat -ano | findstr :PORT(Windows) to confirm a listener exists. - Check whether the service process is alive:
ps aux | grep <service>or check systemd withsystemctl status <service>. - If inside Docker, confirm you are using the Compose service name, not
localhost. Rundocker compose psto check service status and health. - Try connecting independently of Node.js:
curl http://127.0.0.1:PORT/ortelnet 127.0.0.1 PORTto isolate whether this is a Node.js issue or a general connectivity issue. - Verify environment variables are loaded: add
console.log(process.env.DB_HOST, process.env.DB_PORT)before the connection call. - Check recent logs for the target service to see if it crashed:
journalctl -u postgresql --since "5 minutes ago"ordocker compose logs postgres. - If connecting to a remote host, test with
nc -zv hostname PORTto rule out firewall or security group issues. - On Node.js 18+, if using the built-in
fetch, try replacinglocalhostwith127.0.0.1to avoid IPv6 resolution. - Check
NO_PROXYenvironment variable if behind a corporate proxy — localhost traffic should bypass the proxy.
# Quick connectivity test outside Node.js
curl -v http://127.0.0.1:3000/
telnet 127.0.0.1 5432
nc -zv 127.0.0.1 6379
# Test MongoDB connectivity
mongosh --eval "db.adminCommand('ping')" mongodb://127.0.0.1:27017
# Test Redis connectivity
redis-cli -h 127.0.0.1 -p 6379 ping
# Check Docker Compose service health
docker compose ps
docker compose logs --tail=50 postgres
# Enable Node.js network debug output
NODE_DEBUG=net node app.js 2>&1 | grep -E "(ECONNREFUSED|connect|error)"
Frequently Asked Questions
What is ECONNREFUSED in Node.js?
ECONNREFUSED stands for Error CONNection REFUSEd. Node.js throws it as Error: connect ECONNREFUSED 127.0.0.1:PORT when the operating system actively rejects a TCP connection attempt with a TCP RST packet, because no process is listening on the target host and port. Unlike ETIMEDOUT (no response) or ECONNRESET (mid-session close), ECONNREFUSED is immediate — the connection was never established.
How do I fix Error: connect ECONNREFUSED 127.0.0.1:3000?
First verify the target service is actually running: use lsof -i :3000 (macOS/Linux) or netstat -ano | findstr :3000 (Windows). If nothing is listening, start the service. Then confirm your host, port, and protocol in the connection string match the running service. If inside Docker, replace localhost with the Compose service name.
Why does ECONNREFUSED happen in Docker Compose?
Inside Docker Compose, each container has its own network namespace. localhost resolves to the container itself — not to sibling services. Replace localhost with the Compose service name (e.g., DB_HOST=postgres instead of DB_HOST=localhost). Also use condition: service_healthy with a healthcheck in depends_on to prevent startup race conditions.
Why do I get ECONNREFUSED ::1:3000 when I use localhost?
On systems with IPv6 enabled, localhost can resolve to ::1 (IPv6 loopback) instead of 127.0.0.1 (IPv4). If your server only listens on IPv4, the connection to ::1 is refused. Fix: use 127.0.0.1 explicitly in your connection string, or start Node.js with --dns-result-order=ipv4first, or bind your server to :: (dual-stack).
How do I fix ECONNREFUSED with Node.js 18 fetch API?
Node.js 18+ introduced a built-in fetch that uses the Happy Eyeballs algorithm and may prefer IPv6. If your server only binds to 127.0.0.1 (IPv4), fetch('http://localhost:PORT') can get ECONNREFUSED when it connects to ::1. Fix: use fetch('http://127.0.0.1:PORT') explicitly, or start Node.js with node --dns-result-order=ipv4first server.js.
What is the difference between ECONNREFUSED and ETIMEDOUT?
ECONNREFUSED: the OS immediately replied with a TCP RST — nothing is listening on that port. The response is instant. ETIMEDOUT: no reply arrived within the timeout window — a firewall is dropping packets silently, or the host is unreachable. ECONNREFUSED is faster to diagnose because it is immediate and points to a local or same-network issue.
How do I add retry logic for ECONNREFUSED in Node.js?
Wrap the connection attempt in a loop that catches errors where err.code === 'ECONNREFUSED' and waits with exponential backoff before the next attempt. Only catch ECONNREFUSED — throw immediately for auth errors or other non-transient failures. For databases: Mongoose has built-in serverSelectionTimeoutMS retry; ioredis has a retryStrategy callback. See the code examples above for a full implementation.
Related Errors
EADDRINUSE: address already in use— port conflict when starting a server; the opposite problem — something is already listening on the port you want to bindECONNRESET: connection reset by peer— the connection was established but then forcibly closed mid-session; contrast with ECONNREFUSED which never establishes a connectionETIMEDOUT: connect ETIMEDOUT— the connection attempt timed out with no response; a firewall is dropping packets silently rather than actively refusing themENOTFOUND: getaddrinfo ENOTFOUND— DNS resolution failed before a TCP connection could be attempted; the hostname does not exist