---
# === IDENTITY ===
id: software/debugging/nodejs-econnrefused/2026
canonical_question: "How do I debug ECONNREFUSED errors in Node.js?"
aliases:
  - "Node.js ECONNREFUSED"
  - "Node.js connection refused"
  - "connect ECONNREFUSED 127.0.0.1"
  - "Error connect ECONNREFUSED"
  - "Node.js cannot connect to database"
  - "Node.js ECONNRESET"
  - "Node.js ETIMEDOUT connection"
  - "Node.js ENOTFOUND"
  - "Docker ECONNREFUSED localhost"
  - "Kubernetes ECONNREFUSED service"
entity_type: software_reference
domain: software > debugging > nodejs_econnrefused
region: global
jurisdiction: global
temporal_scope: 2020-2026

# === VERIFICATION ===
last_verified: 2026-02-23
confidence: 0.93
version: 2.0
first_published: 2026-02-17

# === TEMPORAL VALIDITY ===
temporal_validity:
  status: stable
  last_breaking_change: "Node.js 23.0 (2024-10) — require(esm) enabled by default; Node.js 19 (2022-10) — net.setDefaultAutoSelectFamily(true) IPv6-first"
  next_review: 2026-08-22
  change_sensitivity: low

# === CONSTRAINTS ===
constraints:
  - "ECONNREFUSED is a TCP RST response — the target OS received the SYN packet and actively rejected it; do not confuse with ETIMEDOUT (no response, possibly firewalled) or ENOTFOUND (DNS failure)"
  - "Never retry ECONNREFUSED indefinitely in production — always cap retries and fail fast with a clear error after the limit; unbounded retries cause cascading failures"
  - "In Docker Compose, localhost inside a container resolves to the container's own loopback — always use the Docker service name (e.g., db, redis) as the hostname for container-to-container communication"
  - "Node.js 19+ defaults to IPv6-first (autoSelectFamily) — services listening only on 127.0.0.1 (IPv4) may refuse connections from localhost if it resolves to ::1 (IPv6); force IPv4 with host: '127.0.0.1'"
  - "Cloud databases (AWS RDS, GCP Cloud SQL, Azure Database) often require SSL/TLS — connection may be refused without ssl: { rejectUnauthorized: true } or proper CA certificate configuration"
  - "Connection pool .connect() clients must always be released in a finally block — leaked clients exhaust the pool and produce ECONNREFUSED-like symptoms under load"

# === SKIP CONDITIONS ===
skip_this_unit_if:
  - condition: "Error is ECONNRESET during an active request (connection was established then dropped)"
    use_instead: "Check server-side keepalive timeouts, load balancer idle timeout, or server crash — not a connection-refused issue"
  - condition: "Error is ETIMEDOUT (connection attempt hangs with no response)"
    use_instead: "Check network routing, firewall rules, or security groups — the target is unreachable, not actively refusing"
  - condition: "Error is ENOTFOUND (DNS lookup failure)"
    use_instead: "Check DNS configuration, hostname spelling, /etc/hosts, or CoreDNS in Kubernetes — this is a name resolution problem"

# === AGENT HINTS ===
inputs_needed:
  - key: runtime_environment
    question: "Where is your Node.js application running?"
    type: choice
    options: ["Bare metal / VM", "Docker container", "Kubernetes pod", "Serverless (Lambda/Cloud Functions)"]
  - key: target_service
    question: "What service is Node.js trying to connect to?"
    type: choice
    options: ["PostgreSQL", "MySQL", "Redis", "MongoDB", "External HTTP API", "Another Node.js service", "Other"]
  - key: error_timing
    question: "When does the error occur?"
    type: choice
    options: ["On app startup (first connection)", "Intermittently under load", "After a period of inactivity", "Always / every request"]

# === DISTRIBUTION ===
canonical_source: "https://knowledgelib.io/software/debugging/nodejs-econnrefused/2026"
suggested_citation: "Source: knowledgelib.io — AI Knowledge Library (verified 2026-02-23)"

# === RELATED UNITS ===
related_kos:
  related_to:
    - id: "software/debugging/nodejs-memory-leaks/2026"
      label: "Node.js Memory Leak Debugging"
    - id: "software/debugging/nodejs-unhandled-promises/2026"
      label: "Node.js Unhandled Promise Rejections"
  solves:
    - id: "software/debugging/docker-compose-networking/2026"
      label: "Docker Compose Networking Troubleshooting"
  often_confused_with:
    - id: "software/debugging/nodejs-etimedout/2026"
      label: "Node.js ETIMEDOUT Errors (different root cause: unreachable, not refused)"
    - id: "software/debugging/nodejs-econnreset/2026"
      label: "Node.js ECONNRESET Errors (connection dropped mid-flight, not refused)"

# === SOURCES (5-8 authoritative sources) ===
sources:
  - id: src1
    title: "Node.js — Errors: Common System Errors"
    author: Node.js Foundation
    url: https://nodejs.org/api/errors.html#common-system-errors
    type: official_docs
    published: 2025-10-01
    reliability: authoritative
  - id: src2
    title: "Better Stack — 16 Common Errors in Node.js and How to Fix Them"
    author: Better Stack
    url: https://betterstack.com/community/guides/scaling-nodejs/nodejs-errors/
    type: technical_blog
    published: 2025-06-01
    reliability: high
  - id: src3
    title: "Docker — Networking Overview: Container-to-Container Communication"
    author: Docker Inc.
    url: https://docs.docker.com/network/
    type: official_docs
    published: 2025-09-01
    reliability: authoritative
  - id: src4
    title: "Python Speed — Connection Refused? Docker Networking and How It Impacts Your Image"
    author: Itamar Turner-Trauring
    url: https://pythonspeed.com/articles/docker-connection-refused/
    type: technical_blog
    published: 2025-03-01
    reliability: high
  - id: src5
    title: "AWS — Exponential Backoff and Jitter"
    author: AWS Architecture Blog
    url: https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/
    type: technical_blog
    published: 2024-01-01
    reliability: high
  - id: src6
    title: "Node.js — Net Module: connect() and Socket Events"
    author: Node.js Foundation
    url: https://nodejs.org/api/net.html
    type: official_docs
    published: 2025-10-01
    reliability: authoritative
  - id: src7
    title: "node-postgres — Connection and Pooling"
    author: Brian Carlson
    url: https://node-postgres.com/features/connecting
    type: official_docs
    published: 2025-01-01
    reliability: high
  - id: src8
    title: "Node.js — Node.js 23.0.0 Release Notes (require(esm), networking changes)"
    author: Node.js Foundation
    url: https://nodejs.org/en/blog/release/v23.0.0
    type: official_docs
    published: 2024-10-16
    reliability: authoritative
---

# How to Debug ECONNREFUSED Errors in Node.js

## TL;DR

- **Bottom line**: `ECONNREFUSED` means Node.js reached the target IP but nothing was listening on the specified port — the OS actively rejected the TCP connection with a RST packet. The 5 most common causes: (1) target service not running, (2) wrong host/port in connection config, (3) Docker networking (`localhost` inside a container points to itself), (4) service bound to `127.0.0.1` instead of `0.0.0.0`, (5) firewall blocking the port.
- **Key tool/command**: `curl -v telnet://host:port` or `nc -zv host port` to test raw TCP connectivity outside of Node.js. If this fails too, the problem is not in your code.
- **Watch out for**: Using `localhost` inside Docker containers — it resolves to the container's own loopback, not the host machine or other containers. Use the Docker service name from `docker-compose.yml` instead.
- **Works with**: Node.js 18+, all current versions (18 LTS, 20 LTS, 22 LTS, 23 Current). Applies to HTTP clients (`axios`, `fetch`, `got`, `undici`), database drivers (`pg`, `mysql2`, `mongoose`, `ioredis`), and raw TCP sockets.

## Constraints
<!-- Agents: read this section before recommending any action from this unit.
     These are hard constraints that must not be violated. -->

- ECONNREFUSED is a TCP RST response — the target OS received the SYN and actively rejected it. Do not confuse with ETIMEDOUT (no response, possibly firewalled) or ENOTFOUND (DNS failure). [src1]
- Never retry ECONNREFUSED indefinitely in production — always cap retries and fail fast with a clear error after the limit. Unbounded retries cause cascading failures and resource exhaustion. [src5]
- In Docker Compose, `localhost` inside a container resolves to the container's own loopback — always use the Docker service name (e.g., `db`, `redis`) as the hostname. [src3, src4]
- Node.js 19+ defaults to IPv6-first (`autoSelectFamily`) — services listening only on `127.0.0.1` (IPv4) may refuse connections from `localhost` if it resolves to `::1` (IPv6). Force IPv4 with `host: '127.0.0.1'`. [src1, src6]
- Cloud databases (RDS, Cloud SQL) often require SSL/TLS — connection may be refused without proper SSL configuration. [src7]
- Connection pool `.connect()` clients must always be released in a `finally` block — leaked clients exhaust the pool. [src7]

## Quick Reference

| # | Cause | Likelihood | Signature | Fix |
|---|---|---|---|---|
| 1 | Target service not running | ~30% of cases | `connect ECONNREFUSED 127.0.0.1:PORT` | Start the service: `systemctl start postgres`, `docker start container` [src1, src2] |
| 2 | Wrong host or port | ~20% of cases | `connect ECONNREFUSED WRONG_IP:PORT` | Verify connection string matches actual service host:port [src2] |
| 3 | Docker: using `localhost` instead of service name | ~15% of cases | `connect ECONNREFUSED 127.0.0.1:PORT` from container | Use Docker Compose service name (e.g., `db`) as host [src3, src4] |
| 4 | Service bound to `127.0.0.1` only | ~10% of cases | Works locally, fails from another machine/container | Bind service to `0.0.0.0` or specific IP [src3, src4] |
| 5 | Firewall blocking the port | ~8% of cases | `nc` times out or refuses from outside | Open port in firewall: `ufw allow PORT` or security group rule [src2] |
| 6 | Service still starting up | ~7% of cases | Error on first request, works seconds later | Add retry logic with exponential backoff; use Docker healthchecks [src5] |
| 7 | Connection pool exhausted | ~4% of cases | `ECONNREFUSED` after sustained load | Increase pool `max`; fix connection leaks (unreleased clients) [src7] |
| 8 | DNS resolution failure | ~3% of cases | `ENOTFOUND` or `ECONNREFUSED` on hostname | Check DNS, use IP directly to test; verify `/etc/hosts` [src1] |
| 9 | Port already in use by another process | ~2% of cases | Target service fails to start silently | `lsof -i :PORT` or `netstat -tuln | grep PORT` to find conflict [src2] |
| 10 | SSL/TLS port mismatch | ~1% of cases | Connecting with HTTP to HTTPS port or vice versa | Match protocol to port (e.g., 443 = HTTPS, 5432 = plain Postgres) [src7] |
| 11 | IPv6/IPv4 mismatch (Node.js 19+) | ~1% of cases | `connect ECONNREFUSED ::1:PORT` | Force IPv4: `host: '127.0.0.1'` or set `autoSelectFamily: false` [src1, src6] |

## Decision Tree

> Full script: [decision-tree.txt](scripts/decision-tree.txt) (25 lines)

```
START
├── Can you reach the service from the SAME machine (curl/nc)?
│   ├── NO → Service is not running or port is wrong
│   │   ├── Check: is the service process running? (ps aux | grep service)
│   │   │   ├── NOT RUNNING → Start it [src1]
│   │   │   └── RUNNING → Check which port it's listening on (netstat -tuln) [src2]
│   │   └── Port conflict? → Another process using the port (lsof -i :PORT)
│   └── YES → Network/config issue between Node.js and the service ↓
├── Is Node.js running inside a Docker container?
│   ├── YES → Are you using "localhost" or "127.0.0.1" as host?
│   │   ├── YES → Change to Docker service name from docker-compose.yml [src3, src4]
│   │   └── NO → Check both containers are on the same Docker network [src3]
│   └── NO ↓
├── Is Node.js running in Kubernetes?
│   ├── YES → Use service-name.namespace.svc.cluster.local as host [src3]
│   └── NO ↓
├── Does the service bind to 0.0.0.0 or 127.0.0.1?
│   ├── 127.0.0.1 → Change to 0.0.0.0 if external access needed [src3]
│   └── 0.0.0.0 ↓
├── Is there a firewall between Node.js and the service?
│   ├── YES → Open the port in firewall/security group [src2]
│   └── NO ↓
├── Does the error happen only on first connection attempt?
│   ├── YES → Service still starting. Add retry with backoff [src5]
│   └── NO ↓
├── Does the error show ::1 (IPv6) but service listens on 127.0.0.1 (IPv4)?
│   ├── YES → Force IPv4: host: '127.0.0.1' or autoSelectFamily: false [src1, src6]
│   └── NO ↓
└── Does it happen under load?
    ├── YES → Connection pool exhaustion. Increase max, fix leaks [src7]
    └── NO → Check environment variables for host/port config
```

## Step-by-Step Guide

### 1. Read the full error message

The ECONNREFUSED error always includes the target IP and port. This tells you exactly where Node.js tried to connect. [src1]

```
Error: connect ECONNREFUSED 127.0.0.1:5432
    at TCPConnectWrap.afterConnect [as oncomplete] (node:net:1607:16)
    code: 'ECONNREFUSED',
    syscall: 'connect',
    address: '127.0.0.1',
    port: 5432
```

**Key fields**: `address` (where Node.js tried to connect), `port` (which port), `code` (the specific error). In Node.js 22+, check `error.cause` for chained errors that provide additional context. [src1, src8]

**Verify**: Note the `address` and `port` — are they what you expect?

### 2. Test raw TCP connectivity

Before debugging your Node.js code, verify that the port is reachable at all. [src2]

```bash
# Test TCP connection (most reliable)
nc -zv hostname 5432
# Expected: "Connection to hostname 5432 port [tcp/postgresql] succeeded!"

# Alternative with curl
curl -v telnet://hostname:5432

# Check what's listening on the port (run on the target machine)
# Linux
ss -tuln | grep 5432
netstat -tuln | grep 5432
lsof -i :5432

# macOS
lsof -nP -iTCP:5432 | grep LISTEN

# Windows
netstat -an | findstr "5432"
```

**Verify**: If `nc` fails too, the problem is at the OS/network level, not in Node.js.

### 3. Verify the service is running and listening

Check that the target service is actually started and accepting connections. [src1, src2]

```bash
# PostgreSQL
pg_isready -h localhost -p 5432

# MySQL
mysqladmin -h localhost -P 3306 ping

# Redis
redis-cli -h localhost -p 6379 ping

# MongoDB
mongosh --host localhost --port 27017 --eval "db.runCommand({ ping: 1 })"

# Generic: check if process is running
ps aux | grep postgres
systemctl status postgresql

# Docker: check container status
docker ps | grep postgres
docker logs postgres-container
```

**Verify**: Service reports "accepting connections" or responds to ping.

### 4. Fix Docker networking issues

The #1 Docker-specific cause: `localhost` inside a container refers to that container's own loopback, not other containers or the host. [src3, src4]

```yaml
# docker-compose.yml
services:
  app:
    build: .
    depends_on:
      db:
        condition: service_healthy  # Wait for DB to be ready
    environment:
      # WRONG — localhost is the app container itself
      # DATABASE_URL: postgres://user:pass@localhost:5432/mydb

      # CORRECT — use the service name "db" as hostname
      DATABASE_URL: postgres://user:pass@db:5432/mydb

  db:
    image: postgres:16
    environment:
      POSTGRES_PASSWORD: pass
      POSTGRES_DB: mydb
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      timeout: 5s
      retries: 5
```

For connecting to a service on the host from inside a container:
```yaml
# docker-compose.yml
services:
  app:
    build: .
    extra_hosts:
      - "host.docker.internal:host-gateway"  # Linux
    environment:
      # Connect to service running on the Docker host
      DATABASE_URL: postgres://user:pass@host.docker.internal:5432/mydb
```

**Verify**: `docker exec app-container nc -zv db 5432` succeeds.

### 5. Check service binding address

Some services bind to `127.0.0.1` by default, making them only accessible from the same machine. [src3, src4]

```bash
# PostgreSQL: check listen_addresses in postgresql.conf
grep listen_addresses /etc/postgresql/16/main/postgresql.conf
# Should be: listen_addresses = '*'  (or '0.0.0.0')

# MySQL: check bind-address in my.cnf
grep bind-address /etc/mysql/mysql.conf.d/mysqld.cnf
# Should be: bind-address = 0.0.0.0

# Redis: check bind in redis.conf
grep "^bind" /etc/redis/redis.conf
# For Docker: bind 0.0.0.0

# Node.js HTTP server: ensure binding to 0.0.0.0
server.listen(3000, '0.0.0.0');  // Not just server.listen(3000)
```

**Verify**: `ss -tuln | grep PORT` shows `0.0.0.0:PORT` not `127.0.0.1:PORT`.

### 6. Handle IPv6/IPv4 mismatch (Node.js 19+)

Starting with Node.js 19, the `autoSelectFamily` option defaults to `true`, which tries IPv6 first. If the service only listens on IPv4 `127.0.0.1`, connections from `localhost` (which resolves to `::1` on dual-stack systems) will be refused. [src1, src6]

```javascript
// Force IPv4 for database connections
const pool = new Pool({
  host: '127.0.0.1',  // Explicit IPv4, not 'localhost'
  port: 5432,
});

// Or disable autoSelectFamily for net.connect
const socket = net.connect({
  host: 'localhost',
  port: 5432,
  autoSelectFamily: false,  // Disable IPv6-first behavior
});
```

**Verify**: Change `localhost` to `127.0.0.1` in your connection string — if the error disappears, it was an IPv6/IPv4 mismatch.

### 7. Implement retry logic with exponential backoff

For transient connection failures (service starting up, brief network blips), add retry logic. [src5]

```javascript
async function connectWithRetry(connectFn, options = {}) {
  const {
    maxRetries = 5,
    baseDelay = 1000,  // 1 second
    maxDelay = 30000,  // 30 seconds
    retryableErrors = ['ECONNREFUSED', 'ECONNRESET', 'ETIMEDOUT', 'ENOTFOUND'],
  } = options;

  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      return await connectFn();
    } catch (error) {
      if (!retryableErrors.includes(error.code) || attempt === maxRetries) {
        throw error;
      }
      // Exponential backoff with jitter
      const delay = Math.min(
        baseDelay * Math.pow(2, attempt - 1) + Math.random() * 1000,
        maxDelay
      );
      console.warn(
        `Connection attempt ${attempt}/${maxRetries} failed (${error.code}). ` +
        `Retrying in ${Math.round(delay)}ms...`
      );
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
}

// Usage with PostgreSQL
const { Pool } = require('pg');
const pool = new Pool({ connectionString: process.env.DATABASE_URL });

const client = await connectWithRetry(() => pool.connect());
```

**Verify**: App logs show retry attempts and eventually connects when service becomes available.

### 8. Check firewall and security groups

If the service is running and binding correctly but still unreachable from another machine. [src2]

```bash
# Linux: check iptables/ufw
sudo ufw status
sudo ufw allow 5432/tcp

# Check iptables directly
sudo iptables -L -n | grep 5432

# AWS: check Security Group allows inbound on the port
aws ec2 describe-security-groups --group-ids sg-xxx \
  --query 'SecurityGroups[].IpPermissions[?ToPort==`5432`]'

# Test from the connecting machine
curl -v telnet://target-ip:5432 --connect-timeout 5
# If it hangs → firewall. If "Connection refused" → service issue.
```

**Verify**: `nc -zv target-ip port` succeeds from the Node.js host.

## Code Examples

### Database connection with retry and health monitoring

> Full script: [database-connection-with-retry-and-health-monitori.js](scripts/database-connection-with-retry-and-health-monitori.js) (53 lines)

```javascript
// Input:  App crashing on ECONNREFUSED when DB is temporarily unavailable
// Output: Resilient connection with retry, health checks, graceful degradation
const { Pool } = require('pg');
class ResilientDatabase {
  constructor(connectionString, options = {}) {
# ... (see full script)
```

### HTTP client with smart retry for upstream APIs

> Full script: [http-client-with-smart-retry-for-upstream-apis.js](scripts/http-client-with-smart-retry-for-upstream-apis.js) (43 lines)

```javascript
// Input:  API calls failing intermittently with ECONNREFUSED/ECONNRESET
// Output: Axios client with configurable retry and circuit breaker
const axios = require('axios');
function createResilientClient(baseURL, options = {}) {
  const client = axios.create({
# ... (see full script)
```

### Docker Compose: Full-stack app with proper networking

> Full script: [docker-compose-full-stack-app-with-proper-networki.yml](scripts/docker-compose-full-stack-app-with-proper-networki.yml) (49 lines)

```yaml
# Input:  docker-compose.yml where app gets ECONNREFUSED to database
# Output: Properly networked services with health checks
version: '3.9'
services:
  app:
# ... (see full script)
```

### Node.js native fetch with retry (Node.js 22+)

```javascript
// Input:  API endpoint that may be temporarily unavailable
// Output: Resilient fetch wrapper using native fetch + AbortSignal.timeout
async function fetchWithRetry(url, options = {}) {
  const { maxRetries = 3, baseDelay = 1000, timeout = 5000, ...fetchOpts } = options;

  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      const response = await fetch(url, {
        ...fetchOpts,
        signal: AbortSignal.timeout(timeout),
      });
      return response;
    } catch (error) {
      const isRetryable = error.cause?.code === 'ECONNREFUSED'
        || error.cause?.code === 'ECONNRESET'
        || error.name === 'TimeoutError';
      if (!isRetryable || attempt === maxRetries) throw error;
      const delay = baseDelay * Math.pow(2, attempt - 1) + Math.random() * 500;
      await new Promise(r => setTimeout(r, delay));
    }
  }
}

// Usage
const response = await fetchWithRetry('http://localhost:3000/api/health', {
  maxRetries: 5,
  timeout: 3000,
});
```

## Anti-Patterns

### Wrong: Using localhost in Docker containers

```javascript
// BAD — localhost inside a container = the container itself, not the DB [src3, src4]
const pool = new Pool({
  host: 'localhost',    // Points to app container, not DB container
  port: 5432,
  database: 'mydb',
});
// Error: connect ECONNREFUSED 127.0.0.1:5432
```

### Correct: Use Docker service name

```javascript
// GOOD — use the service name from docker-compose.yml [src3, src4]
const pool = new Pool({
  host: process.env.DB_HOST || 'postgres',  // Docker service name
  port: parseInt(process.env.DB_PORT || '5432'),
  database: 'mydb',
});
```

### Wrong: Crashing on first connection failure

```javascript
// BAD — app exits if DB isn't ready yet [src5]
const { Pool } = require('pg');
const pool = new Pool({ connectionString: process.env.DATABASE_URL });

// If DB is still starting → ECONNREFUSED → crash
const result = await pool.query('SELECT NOW()');
console.log('Connected at', result.rows[0].now);
```

### Correct: Retry with backoff on startup

```javascript
// GOOD — wait for DB to become available [src5]
const { Pool } = require('pg');
const pool = new Pool({ connectionString: process.env.DATABASE_URL });

async function waitForDB(maxRetries = 10) {
  for (let i = 1; i <= maxRetries; i++) {
    try {
      const result = await pool.query('SELECT NOW()');
      console.log('Connected at', result.rows[0].now);
      return;
    } catch (err) {
      if (i === maxRetries) throw new Error(`DB unavailable after ${maxRetries} attempts: ${err.message}`);
      const delay = Math.min(1000 * Math.pow(2, i - 1), 15000);
      console.warn(`DB not ready (${err.code}), retry ${i}/${maxRetries} in ${delay}ms`);
      await new Promise(r => setTimeout(r, delay));
    }
  }
}

await waitForDB();
```

### Wrong: Hardcoding connection details

```javascript
// BAD — different values needed per environment [src7]
const pool = new Pool({
  host: '192.168.1.50',
  port: 5432,
  user: 'admin',
  password: 'secret123',
  database: 'production_db',
});
```

### Correct: Use environment variables

```javascript
// GOOD — works in dev, Docker, staging, production [src7]
const pool = new Pool({
  connectionString: process.env.DATABASE_URL,
  // Or individual env vars:
  // host: process.env.DB_HOST,
  // port: parseInt(process.env.DB_PORT || '5432'),
  // user: process.env.DB_USER,
  // password: process.env.DB_PASSWORD,
  // database: process.env.DB_NAME,
});
```

### Wrong: Using depends_on without health checks

```yaml
# BAD — depends_on only waits for container start, not service readiness [src3]
services:
  app:
    depends_on:
      - db   # db container starts, but PostgreSQL may not be ready yet
```

### Correct: Use depends_on with service_healthy condition

```yaml
# GOOD — waits for PostgreSQL to actually accept connections [src3]
services:
  app:
    depends_on:
      db:
        condition: service_healthy
  db:
    image: postgres:16
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      timeout: 5s
      retries: 5
      start_period: 10s
```

## Common Pitfalls

- **`localhost` vs `127.0.0.1` vs `::1`**: On dual-stack systems (especially Node.js 19+), `localhost` may resolve to IPv6 `::1` first, while the service only listens on IPv4 `127.0.0.1`. Try `127.0.0.1` explicitly if `localhost` gives ECONNREFUSED. [src1, src2]
- **Docker `depends_on` doesn't mean "ready"**: `depends_on` only waits for the container to start, not for the service inside to be ready. Use `condition: service_healthy` with proper healthchecks. [src3, src4]
- **Port mapping confusion**: In Docker Compose, `ports: "5433:5432"` maps host port 5433 to container port 5432. Container-to-container communication uses the internal port (5432), not the mapped one. [src3]
- **Environment variable not loaded**: `DATABASE_URL` is undefined because `.env` file isn't loaded, or the env var isn't passed into Docker. Debug with `console.log(process.env.DATABASE_URL)`. [src7]
- **Connection pool exhausted**: If you call `pool.connect()` without releasing clients (`.release()`), the pool fills up and new connections hang or get refused. Always release in a `finally` block. [src7]
- **WSL2 networking**: On Windows with WSL2, `localhost` forwarding between Windows and WSL can be unreliable. Use the WSL2 IP (`hostname -I` inside WSL) or `host.docker.internal`. [src4]
- **Kubernetes service DNS**: Use `service-name.namespace.svc.cluster.local` as the host, not `localhost`. Kubernetes pods each have their own network namespace. [src3]
- **Node.js 22+ error.cause**: In Node.js 22+, connection errors from `fetch()` wrap the original system error in `error.cause`. Check `error.cause.code` for `ECONNREFUSED`, not just `error.code`. [src1, src8]

## Diagnostic Commands

> Full script: [diagnostic-commands.sh](scripts/diagnostic-commands.sh) (32 lines)

```bash
# Test TCP connectivity
nc -zv hostname port
curl -v telnet://hostname:port --connect-timeout 5
# Check what's listening on a port
# Linux
# ... (see full script)
```

## Version History & Compatibility

| Version | Status | Behavior | Key Changes |
|---|---|---|---|
| Node.js 23 (Current) | Active | `require(esm)` enabled by default | Networking behavior same as 22; `require()` can now load ESM [src8] |
| Node.js 22 LTS | Active LTS | `ECONNREFUSED` in error.cause chain | Improved error stacks with `error.cause` for chained errors; `fetch()` stable [src1] |
| Node.js 20 LTS | Maintenance | Stable | `fetch()` built-in with connection error codes; `AbortSignal.timeout()` [src1, src6] |
| Node.js 19 | EOL | IPv6-first default | `net.setDefaultAutoSelectFamily(true)` — may cause ECONNREFUSED on IPv4-only services [src6] |
| Node.js 18 LTS | EOL (Apr 2025) | Supported | Built-in `fetch()` (experimental); `net.connect()` improvements [src6] |

## When to Use / When Not to Use

| Use When | Don't Use When | Use Instead |
|---|---|---|
| Error message contains `ECONNREFUSED` | Error is `ECONNRESET` during active request | Check keepalive timeouts, server crashes |
| Cannot connect to database/API at all | Error is `ETIMEDOUT` (connection hangs) | Check network routing, firewall rules |
| Error happens in Docker/container environments | Error is `ENOTFOUND` (DNS failure) | Check DNS configuration, hostname spelling |
| Error happens on app startup before any requests | Error is authentication failure after connecting | Check credentials, certificates |
| Error shows `::1` or IPv6 address | Error is `EPERM` or `EACCES` | Check file permissions, privileged ports (<1024) |

## Important Caveats

- `ECONNREFUSED` is a TCP-level error — it means the target OS received the SYN packet and responded with RST. This is different from `ETIMEDOUT` (no response at all, possibly firewalled) and `ENOTFOUND` (DNS failure).
- In Kubernetes, service names resolve via cluster DNS (`kube-dns` or `CoreDNS`). Use `service-name.namespace.svc.cluster.local` as the host, not `localhost`.
- Some database drivers (like `pg`) have their own internal retry/reconnect logic. Check your driver's documentation before adding redundant retry logic.
- `ECONNREFUSED` on `::1` (IPv6 localhost) when the service listens on `127.0.0.1` (IPv4) is a common trap on Node.js 19+. Force IPv4 with `host: '127.0.0.1'` or disable IPv6 resolution.
- Cloud databases (RDS, Cloud SQL) often require SSL. Connection may be refused if you connect without `ssl: { rejectUnauthorized: false }` or proper certificate configuration.
- Node.js 22+ wraps connection errors from `fetch()` in `error.cause` — check `error.cause.code` for the original system error code, not `error.code` directly.
- CVE-2025-59465 (Node.js TLSSocket ECONNRESET) and CVE-2026-21636 (pipe_wrap connect) are recent security fixes — ensure you are running patched versions (22.13+, 23.6+). [src1]

## Related Units
<!-- Generated from related_kos frontmatter -->

- [Node.js Memory Leak Debugging](/software/debugging/nodejs-memory-leaks/2026)
- [Node.js Unhandled Promise Rejections](/software/debugging/nodejs-unhandled-promises/2026)
- [Docker Compose Networking Troubleshooting](/software/debugging/docker-compose-networking/2026)
- [Node.js ETIMEDOUT Errors](/software/debugging/nodejs-etimedout/2026)
- [Node.js ECONNRESET Errors](/software/debugging/nodejs-econnreset/2026)
