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.
Related error: ECONNREFUSED vs EADDRINUSE
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
docker ps/systemctl status— is the service alive? (check for crash loops)ss -tlnp | grep <port>— what address and port are actually being listened on?- Bound to
127.0.0.1or0.0.0.0? Remote connections need the latter - Container → host service: use
host.docker.internal, not localhost - Cloud: check security group rules; on-box: check iptables REJECT
- 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.
Related Tools
Related Articles
Permission denied (publickey): 6 Reasons Git Push Fails Over SSH (and the Fix for Each)
git clone or push rejected with Permission denied (publickey) fatal: Could not read from remote repository? Covers missing keygen, key not loaded in the agent, public key not added to GitHub/GitLab, multi-account key routing with ~/.ssh/config, deploy key limits, and wrong remote URLs — with ssh -v diagnostics.
error:0308010C digital envelope routines::unsupported — Fixing Node 17+ Breakage in Older Projects
Node 17+ crashes webpack 4 builds with error:0308010C:digital envelope routines::unsupported because OpenSSL 3.0 removed MD4. Three fixes compared: --openssl-legacy-provider quick unblock, upgrading to webpack 5 as the real fix, and pinning Node 16 as a stopgap.
Docker permission denied on docker.sock: the Right Fix (and Its Security Cost)
docker commands fail with permission denied while trying to connect to the Docker daemon socket? The cause: your user isn't in the docker group. One-step fix with usermod -aG, the newgrp trick to avoid re-login, why chmod 777 is a trap, and why docker group membership equals passwordless root — plus the rootless alternative.