Node.js Error

Error: connect ECONNREFUSED 127.0.0.1:3000

error code: ECONNREFUSED

Complete reference — what it means, why Node.js cannot connect, and how to fix it with service checks, config corrections, Docker networking, IPv6 fixes, retry logic, and database reconnection patterns.

Quick Answer: 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.

Exact error messages you will see:
Error: connect ECONNREFUSED 127.0.0.1:3000
Error: connect ECONNREFUSED 127.0.0.1:5432
Error: connect ECONNREFUSED 127.0.0.1:6379
Error: connect ECONNREFUSED 127.0.0.1:27017
Error: connect ECONNREFUSED ::1:3000
Error: 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

CauseWhy 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
Race condition at startup: If your Node.js app and a dependency (e.g., PostgreSQL) start simultaneously, the app may attempt to connect before the database is ready. This is especially common with Docker Compose. Use retry logic (Fix 5) or a health-check dependency in your 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,
});
Always include the protocol in URLs: Calling 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 is not enough: 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

  1. Read err.address and err.port from the error object — they tell you exactly what Node.js tried to connect to.
  2. Run lsof -i :PORT (macOS/Linux) or netstat -ano | findstr :PORT (Windows) to confirm a listener exists.
  3. Check whether the service process is alive: ps aux | grep <service> or check systemd with systemctl status <service>.
  4. If inside Docker, confirm you are using the Compose service name, not localhost. Run docker compose ps to check service status and health.
  5. Try connecting independently of Node.js: curl http://127.0.0.1:PORT/ or telnet 127.0.0.1 PORT to isolate whether this is a Node.js issue or a general connectivity issue.
  6. Verify environment variables are loaded: add console.log(process.env.DB_HOST, process.env.DB_PORT) before the connection call.
  7. Check recent logs for the target service to see if it crashed: journalctl -u postgresql --since "5 minutes ago" or docker compose logs postgres.
  8. If connecting to a remote host, test with nc -zv hostname PORT to rule out firewall or security group issues.
  9. On Node.js 18+, if using the built-in fetch, try replacing localhost with 127.0.0.1 to avoid IPv6 resolution.
  10. Check NO_PROXY environment 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)"
Do not silently swallow ECONNREFUSED: Catching the error without logging or retrying hides the fact that a required dependency is unavailable. Your application should fail fast and loudly on startup if a critical service (database, message broker) is unreachable, rather than silently degrading or returning incorrect results to users.

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