/인프라/connection refused / ECONNREFUSED 127.0.0.1: a 30-second diagnostic runbook
Infrastructureconnection refusedECONNREFUSED

connection refused / ECONNREFUSED 127.0.0.1: a 30-second diagnostic runbook

A 4-step runbook that triages connection refused and ECONNREFUSED 127.0.0.1 in 30 seconds. Diagnose a downed server, port mismatch, firewall, or bind address (127.0.0.1 vs 0.0.0.0) with copy-paste commands, including curl, Node, Go, and psq

connection refused / ECONNREFUSED 127.0.0.1: a 30-second diagnostic runbook

connection refused / ECONNREFUSED 127.0.0.1: a 30-second triage runbook

connection refused is not "no response" — it is an active rejection

Right after a deploy, when curl spits out Connection refused or a Node app dies with ECONNREFUSED 127.0.0.1:5432, the first thing you need to do is understand exactly what this error is.

The key is one sentence. connection refused means the packet reached the target, and the target immediately replied with a TCP RST saying "nobody is listening on that port." In other words, the network is fine. The path is not blocked (that would be a timeout) — you arrived and got turned away at the door. That one fact completely changes the direction of your diagnosis.

First, distinguish it from neighboring errors

Error signalTCP behaviorFirst-line suspected cause
connection refusedImmediate RST received (fast)Process not running / port or bind mismatch
timeout (no route, hang)No response; wait seconds to tens of secondsFirewall DROP / security group / routing
EADDRINUSEBind failure at server startA process already occupying that port
502 Bad GatewayProxy is up but upstream refusedBackend is in state 1 or 2 above

If it's a timeout, start by suspecting the firewall/routing. If it's refused, it is almost always a server-side problem. Refusal is fast; blocking (DROP) is slow. That speed difference alone gets you halfway there.

30-second triage runbook: a 4-step decision tree

Follow this from top to bottom the moment you see the error.

CODE
① Is the process running?   → ss -tlnp / systemctl status
      └ No  → start the server (root cause confirmed)
      └ Yes ↓
② Is the port/bind correct?    → 127.0.0.1:PORT vs 0.0.0.0:PORT in ss output
      └ Listening only on 127.0.0.1 but connecting from outside → change the bind (root cause confirmed)
      └ Correct ↓
③ Blocked in the middle?         → nc -zv / ufw status / docker ps port mapping
      └ Firewall/security group/mapping missing → allow the rule (root cause confirmed)
      └ Passes ↓
④ Is the name resolving wrong?    → check whether localhost resolves to ::1 (IPv6)
      └ Listening IPv4 only + IPv6-first resolution → use 127.0.0.1 directly

Copy-paste command table with risk labels

LabelCommandWhat it checks/changes
🟢 Safess -tlnpWhich process is listening on which IP:port
🟢 Safesystemctl status <svc>Whether the service is actually active
🟢 Safenc -zv host portWhether a TCP connection to that port succeeds (refused vs timeout)
🟢 Safetelnet host portSame purpose when nc is unavailable
🟢 Safedocker psPresence of mapping via the PORTS column (0.0.0.0:8080->80)
🟢 Safeufw statusCheck ufw inbound allow rules
🟢 Safeiptables -L -nCheck ACCEPT/DROP rules per chain
🟡 Cautionsudo ufw allow 8080/tcpOpen an inbound port (changes state)
🟡 Cautionsudo systemctl restart <svc>Restart the service (causes downtime)
🟡 Cautiondocker run -p 8080:80 ...Restart with port mapping reset

🟢 you can run freely. 🟡 think twice if this is production.

The core trap: 127.0.0.1 bind vs 0.0.0.0 bind

In the field, 80% of "works locally, refused in a container/remotely" is this one thing. Let's reproduce it.

Bash
# A. Bind to loopback only
python -m http.server --bind 127.0.0.1 8000

# B. Bind to all interfaces
python -m http.server --bind 0.0.0.0 8000

Start each one and run ss -tlnp — the difference is obvious.

CODE
# A의 경우
LISTEN 0  128  127.0.0.1:8000  0.0.0.0:*  users:(("python",pid=...))

# B의 경우
LISTEN 0  128  0.0.0.0:8000    0.0.0.0:*  users:(("python",pid=...))

A is 127.0.0.1:8000, meaning it answers only from inside the same machine. If you connect from another host or from outside a container, the kernel says "no listener on that port for this IP" → immediate RST → connection refused. B, on the other hand, also accepts packets that arrive on an external IP.

One line from the field: Framework defaults are the trap. Flask app.run(), Rails, and some dev servers default to 127.0.0.1. If you get "fine on my laptop, refused only on EC2/Docker," don't start with the code — check the bind address with ss -tlnp first. I've burned days of time on this.

Same error, different faces: message mapping by language/tool

Everything below is the same TCP RST signal. Only the message differs; the diagnostic path is identical.

ToolError message
curlcurl: (7) Failed to connect ... Connection refused
NodeError: connect ECONNREFUSED 127.0.0.1:5432
Godial tcp 127.0.0.1:6379: connect: connection refused
psqlcould not connect to server: Connection refused
redis-cliCould not connect to Redis ... Connection refused

Diagnosis by case

  • curl: curl -v http://host:port → if refused immediately, the server is down or the port is mistyped. Cross-check with nc -zv host port.
  • Node ECONNREFUSED 127.0.0.1:5432: Common when the DB host is localhost but the DB is in a container or remote. Check the host in the connection string, then ss -tlnp | grep 5432.
  • Go dial tcp: Same. Also common: the app starts before a dependent service is up (fix with depends_on / health checks).
  • psql/redis-cli: The server is up but listening only on 127.0.0.1, while the client connects from outside → check the bind or bind settings (redis bind 127.0.0.1, postgres listen_addresses).

Docker and cloud-specific traps

There's a clear reason refused spikes in container environments.

  1. The app inside the container binds to 127.0.0.1 → even docker run -p 8080:80 gets refused. -p forwards traffic from the host to the container's external interface, but if the app is only listening on the container's loopback, there is no listener to reach. Always bind to 0.0.0.0 inside a container.

  2. Container → host connections: localhost inside a container is the container itself, not the host. To reach a host service, use host.docker.internal (Mac/Windows; on recent Linux, --add-host).

  3. AWS security groups / inbound: If a security group is blocking you, you usually get a timeout (DROP), not refused. If you see refused and you're blaming the security group, you're looking in the wrong direction. Exception: an NLB/target group sending health checks to a closed port can surface as refused.

  4. IPv6-first resolution: If localhost resolves to ::1 first but the server is listening only on IPv4 (0.0.0.0), you get refused. Temporary workaround: explicitly use 127.0.0.1.

Conclusion: 4-step checklist card

CODE
[ ] ① Confirm process/port listening with ss -tlnp  (if none → start it)
[ ] ② Check bind address 127.0.0.1 vs 0.0.0.0  (loopback only → 0.0.0.0)
[ ] ③ nc -zv / ufw status / docker ps mapping   (if blocked → allow the rule/mapping)
[ ] ④ Whether localhost resolves to ::1            (IPv6 issue → specify 127.0.0.1)

refused is almost always a server-side problem — specifically "it isn't running, or it's running on the wrong address." Top to bottom, 30 seconds and you're done.

FAQ

Q. How do I quickly tell connection refused from a timeout? A. Run nc -zv host port. If it fails immediately with refused, the server is down or the port is wrong (server-side). If it hangs for several seconds then fails, it's a firewall DROP, security group, or routing problem. Speed is the clue.

Q. It works locally but I only get refused in a container/remotely. A. Nine times out of ten the app is bound to 127.0.0.1. Confirm with ss -tlnp and change it to 0.0.0.0. In a container, the inner app must bind to 0.0.0.0 for -p mapping to work.

Q. ECONNREFUSED 127.0.0.1:5432 — the DB address is correct, so why refused? A. (1) The DB process isn't running, (2) the DB is in a container/remote but the host is set to localhost, or (3) postgres listen_addresses / redis bind is restricted to loopback. Start by checking the actual listen address with ss -tlnp | grep 5432.

확인 정보
✦ ✦ ✦
편집 검토 · Editorial Review

Nodelog는 모든 콘텐츠의 내용과 출처를 공개 전에 검토합니다. 환경(OS·버전)에 따라 결과가 달라질 수 있는 기술 정보는 공식 문서와 함께 확인하며, 검토 기준과 정정 원칙은 편집 정책에서 안내합니다. 오류를 발견하시면 이메일로 제보해 주세요 — 확인 후 신속히 정정합니다.

편집 책임 · Nodelog 기술 편집팀·발행 · ·업데이트 ·

Comments

Be the first to comment.