Skip to content
DevOps2026-09-084 min read

ECONNREFUSED: Connection Refused — 5 Causes Explained (Including Docker)

Symptom: every language, same error

# Node.js
Error: connect ECONNREFUSED 127.0.0.1:3306
    at TCPConnectWrap.afterConnect (node:net:1607:16)

# curl
curl: (7) Failed to connect to 127.0.0.1 port 6379: Connection refused

# Java
java.net.ConnectException: Connection refused (Connection refused)

One-line diagnosis: the network reached the machine, but no process is listening on the port you dialed. The OS replies with a TCP RST. Note the contrast with "timeout": ETIMEDOUT means packets never arrive or get dropped; ECONNREFUSED means they arrived but nobody answered. That distinction alone decides where to investigate.

| Error | Meaning | Direction | |---|---|---| | ECONNREFUSED | Arrived, nobody listening | Check service/port | | ETIMEDOUT | Never arrived or dropped | Check network/firewall |

Five causes, ranked by frequency

1. The service simply isn't running (most common)

systemctl status mysql        # Linux services
docker ps                     # containers up? restarting?
brew services list            # macOS

The high-frequency trap is a container in a crash-restart loop: check whether STATUS in docker ps -a says Restarting (1) 5 seconds ago or Up. The former means the container keeps dying — of course the port is dead.

2. Wrong port from memory

Default port cheat sheet:

| Service | Default port | |---|---| | MySQL | 3306 | | PostgreSQL | 5432 | | Redis | 6379 | | MongoDB | 27017 | | Elasticsearch | 9200 | | RabbitMQ | 5672 |

Two classic mistakes: Redis started with a custom --port override, or MySQL with a modified port= in my.cnf. Trust the actual listener, not your memory:

ss -tlnp | grep -E '3306|6379'    # Linux
lsof -nP -iTCP:3306 -sTCP:LISTEN  # macOS

3. The service binds 127.0.0.1 only, and you connect from outside

ss -tlnp | grep 5432
# LISTEN 127.0.0.1:5432   ← local connections only
# LISTEN 0.0.0.0:5432     ← any source
# LISTEN [::]:5432        ← any source, IPv6

If it's 127.0.0.1:port, every remote connection is refused. Fix per service: comment out bind-address=127.0.0.1 in MySQL, set listen_addresses = '*' in PostgreSQL, change bind 0.0.0.0 in Redis (and always pair that with requirepass — an exposed Redis is ransomware bait).

4. Docker: localhost inside a container is not your host

When an app inside a container connects to a service on the host, 127.0.0.1 refers to the container itself:

# Two correct patterns for an app container reaching a database

# Pattern 1: both on a docker network — use the service name (recommended)
spring.datasource.url: jdbc:mysql://interview-mysql:3306/mydb

# Pattern 2: container reaching a service on the host
# macOS/Windows Docker Desktop has a dedicated hostname:
jdbc:mysql://host.docker.internal:3306/mydb
# Linux hosts (Docker 20.10+) need extra_hosts:
services:
  app:
    extra_hosts:
      - "host.docker.internal:host-gateway"

The reverse direction — host connecting into a container — only works through an explicit port mapping like docker run -p 127.0.0.1:3306:3306. Without -p, curl localhost:3306 from the host is guaranteed ECONNREFUSED.

5. Firewall actively REJECTing

An iptables REJECT rule sends back an RST, which looks exactly like "nobody listening" (DROP would show as a timeout instead):

sudo iptables -L -n | grep -i reject

Cloud servers add another layer: security groups. If the Alibaba/Tencent/AWS console rule doesn't allow the port, you get refused or timed-out connections while everything looks fine inside the server via ss. This is the classic "works locally, dead in the cloud" cause.

The diagnostic route (three layers, bottom-up)

# Layer 1: is anyone listening on the port?
ss -tlnp | grep <port>

# Layer 2: does it connect from this machine?
nc -zv 127.0.0.1 <port>

# Layer 3: does it connect from inside the target network/container?
docker exec -it <container> sh -c 'nc -zv <target-host> <port>'

Nobody at layer 1 → causes 1/2. Local works, remote doesn't → causes 3/5. Fails only inside Docker → cause 4. Three layers and you've localized it.

Two ends of the same port: refused while connecting out is ECONNREFUSED (nothing listening on the far side); refused while binding to listen is EADDRINUSE (the port is already taken on this side). For the latter, see Port already in use: EADDRINUSE troubleshooting.

Checklist

  1. docker ps / systemctl status — is the service alive? (check for crash loops)
  2. ss -tlnp | grep <port> — what address and port are actually being listened on?
  3. Bound to 127.0.0.1 or 0.0.0.0? Remote connections need the latter
  4. Container → host service: use host.docker.internal, not localhost
  5. Cloud: check security group rules; on-box: check iptables REJECT
  6. Distinguish REFUSED (arrived, nobody home) from TIMEOUT (never arrived) — opposite directions

Provided by ToolVault. Related tools: Dockerfile Generator, CIDR Calculator, API Tester. Related reading: Port already in use (EADDRINUSE). See the homepage for more developer tools.


Advertisement