/인프라/connection refused / ECONNREFUSED 127.0.0.1 30초 진단 런북
인프라connection refusedECONNREFUSED

connection refused / ECONNREFUSED 127.0.0.1 30초 진단 런북

'connection refused'·'ECONNREFUSED 127.0.0.1' 에러를 30초에 분기하는 4단계 런북. 서버 미기동·포트 불일치·방화벽·바인드 주소(127.0.0.1 vs 0.0.0.0)를 복붙 명령으로 진단하고 curl·Node·Go·psql 케이스까지 정리했습니다.

connection refused / ECONNREFUSED 127.0.0.1 30초 진단 런북

connection refused / ECONNREFUSED 127.0.0.1 30초 분기 런북

'connection refused'는 "무응답"이 아니라 "적극적 거부"다

배포 직후 curlConnection refused를 뱉거나, Node 앱이 ECONNREFUSED 127.0.0.1:5432로 죽는 순간 가장 먼저 해야 할 일은 이 에러의 정체를 정확히 아는 것입니다.

핵심은 한 줄입니다. connection refused는 패킷이 대상에 도달했고, 대상이 TCP RST로 "그 포트에서 아무도 안 듣는다"고 즉시 응답한 상태입니다. 즉 네트워크는 멀쩡합니다. 길이 막힌 게(timeout) 아니라, 도착했는데 문 앞에서 거절당한 겁니다. 이 한 줄이 진단 방향을 통째로 바꿉니다.

먼저 옆 에러들과 구분하자

에러 신호TCP 동작1차 의심 원인
connection refused즉시 RST 수신 (빠름)프로세스 미기동 / 포트·바인드 불일치
timeout (no route, hang)응답 없음, 수 초~수십 초 대기방화벽 DROP / 보안그룹 / 라우팅
EADDRINUSE서버 기동 시 바인드 실패이미 그 포트를 점유한 프로세스
502 Bad Gateway프록시는 떴으나 업스트림 거부백엔드가 위 1~2번 상태

timeout이면 방화벽/라우팅부터 의심하고, refused면 거의 항상 "서버 측" 문제입니다. 거부는 빠르고, 차단(DROP)은 느립니다. 이 속도 차이만으로도 절반은 잡힙니다.

30초 분기 런북: 4단계 의사결정 트리

에러를 본 직후 위에서 아래로 따라가세요.

CODE
① 프로세스가 떠 있나?   → ss -tlnp / systemctl status
      └ 없음 → 서버 기동 (원인 확정)
      └ 있음 ↓
② 포트/바인드가 맞나?    → ss 출력의 127.0.0.1:PORT vs 0.0.0.0:PORT
      └ 127.0.0.1만 리스닝인데 외부 접속 → 바인드 변경 (원인 확정)
      └ 맞음 ↓
③ 중간에 막혔나?         → nc -zv / ufw status / docker ps 포트매핑
      └ 방화벽/보안그룹/매핑 누락 → 규칙 허용 (원인 확정)
      └ 통과 ↓
④ 이름이 잘못 풀리나?    → localhost가 ::1(IPv6)로 풀리는지 확인
      └ IPv4만 리스닝 + IPv6 우선 해석 → 127.0.0.1 직접 사용

위험도 라벨 복붙 명령표

라벨명령무엇을 확인/변경
🟢 안전ss -tlnp어떤 프로세스가 어떤 IP:포트로 리스닝 중인지
🟢 안전systemctl status <svc>서비스가 실제로 active인지
🟢 안전nc -zv host port해당 포트로 TCP 연결이 되는지(refused/timeout 구분)
🟢 안전telnet host portnc 없을 때 동일 용도
🟢 안전docker psPORTS 컬럼(0.0.0.0:8080->80)로 매핑 유무
🟢 안전ufw statusufw 인바운드 허용 규칙 확인
🟢 안전iptables -L -n체인별 ACCEPT/DROP 규칙 확인
🟡 주의sudo ufw allow 8080/tcp인바운드 포트 개방(상태 변경)
🟡 주의sudo systemctl restart <svc>서비스 재기동(다운타임 발생)
🟡 주의docker run -p 8080:80 ...포트 매핑 재설정으로 재기동

🟢는 마음껏 돌려도 됩니다. 🟡는 운영 환경이면 한 번 더 생각하세요.

핵심 함정: 127.0.0.1 바인드 vs 0.0.0.0 바인드

현업에서 "로컬에선 되는데 컨테이너/원격에선 refused"의 80%는 이거 하나입니다. 직접 재현해 봅시다.

Bash
# A. 루프백에만 바인드
python -m http.server --bind 127.0.0.1 8000

# B. 모든 인터페이스에 바인드
python -m http.server --bind 0.0.0.0 8000

각각 띄우고 ss -tlnp를 보면 차이가 명확합니다.

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는 127.0.0.1:8000, 즉 같은 머신 내부에서만 응답합니다. 다른 호스트나 컨테이너 밖에서 접속하면 커널이 "이 IP로는 그 포트에 리스너 없음" → 즉시 RST → connection refused. 반면 B는 외부 IP로 들어온 패킷도 받습니다.

실무 경험 한 줄: 프레임워크 기본값이 함정입니다. Flask app.run(), Rails, 일부 dev 서버는 기본이 127.0.0.1입니다. "내 노트북에선 멀쩡한데 EC2/도커에서만 거부"가 뜨면 코드부터 보지 말고 ss -tlnp로 바인드 주소부터 확인하세요. 저는 이걸로 날린 시간이 며칠치는 됩니다.

같은 에러, 다른 얼굴: 언어/툴별 메시지 매핑

아래는 전부 동일한 TCP RST 신호입니다. 메시지만 다를 뿐 진단 루트는 같습니다.

도구에러 메시지
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

케이스별 진단

  • curl: curl -v http://host:port → 즉시 refused면 서버 미기동/포트 오타. nc -zv host port로 교차 검증.
  • Node ECONNREFUSED 127.0.0.1:5432: DB 호스트가 localhost인데 DB가 컨테이너/원격에 있는 경우가 흔함. 연결 문자열의 host 확인 후 ss -tlnp | grep 5432.
  • Go dial tcp: 동일. 의존 서비스가 아직 안 떴는데 앱이 먼저 뜬 부팅 순서 문제도 잦음(depends_on/헬스체크로 해결).
  • psql/redis-cli: 서버는 떴는데 127.0.0.1만 리스닝, 클라이언트는 외부에서 접속 → 바인드 또는 bind 설정(redis bind 127.0.0.1, postgres listen_addresses) 확인.

Docker·클라우드 특화 함정

컨테이너 환경에서 refused가 급증하는 이유는 명확합니다.

  1. 컨테이너 내부 앱이 127.0.0.1에 바인드docker run -p 8080:80을 해도 거부됩니다. -p는 호스트 → 컨테이너 외부 인터페이스로 트래픽을 넘기는데, 앱이 컨테이너의 루프백에만 듣고 있으면 도달할 리스너가 없습니다. 컨테이너 안에서는 반드시 0.0.0.0에 바인드하세요.

  2. 컨테이너 → 호스트 접속: 컨테이너 안의 localhost는 호스트가 아니라 컨테이너 자신입니다. 호스트 서비스에 붙으려면 host.docker.internal(Mac/Windows, 최신 리눅스는 --add-host)을 쓰세요.

  3. AWS 보안그룹/인바운드: 다만 보안그룹이 막으면 보통 **timeout(DROP)**이지 refused가 아닙니다. refused인데 보안그룹을 의심한다면 방향이 틀린 겁니다. 단, NLB/타깃그룹이 닫힌 포트로 헬스체크를 보내면 refused가 표면화될 수 있습니다.

  4. IPv6 우선 해석: localhost::1로 먼저 풀리는데 서버가 IPv4(0.0.0.0)에만 리스닝하면 refused가 납니다. 임시 회피는 127.0.0.1을 명시적으로 쓰는 것.

결론: 4단계 체크리스트 카드

CODE
[ ] ① ss -tlnp 로 프로세스/포트 리스닝 확인  (없으면 → 기동)
[ ] ② 바인드 주소 127.0.0.1 vs 0.0.0.0 확인  (루프백만이면 → 0.0.0.0)
[ ] ③ nc -zv / ufw status / docker ps 매핑   (막혔으면 → 규칙·매핑 허용)
[ ] ④ localhost가 ::1로 풀리는지            (IPv6 이슈면 → 127.0.0.1 명시)

refused는 거의 항상 서버 측 문제, 그것도 "안 떴거나 / 엉뚱한 주소에 떴거나"입니다. 위에서 아래로 30초면 끝납니다.

자주 묻는 질문 (FAQ)

Q. connection refused와 timeout, 빠르게 구분하는 법은? A. nc -zv host port를 쳐보세요. 즉시 실패하며 refused가 뜨면 서버 미기동·포트 불일치(서버 측), 수 초 이상 멈췄다가 실패하면 방화벽 DROP·보안그룹·라우팅 문제입니다. 속도가 곧 단서입니다.

Q. 로컬에선 되는데 컨테이너/원격에서만 refused가 나요. A. 십중팔구 앱이 127.0.0.1에 바인드되어 있습니다. ss -tlnp로 확인하고 0.0.0.0으로 바꾸세요. 컨테이너라면 내부 앱의 바인드 주소가 0.0.0.0이어야 -p 매핑이 동작합니다.

Q. ECONNREFUSED 127.0.0.1:5432, DB 주소는 맞는데 왜 거부되나요? A. (1) DB 프로세스가 안 떴거나, (2) DB가 컨테이너/원격에 있는데 host를 localhost로 지정했거나, (3) postgres listen_addresses/redis bind가 루프백으로 제한된 경우입니다. ss -tlnp | grep 5432로 실제 리스닝 주소부터 확인하세요.

✦ ✦ ✦
편집 검토 · Editorial Review

이 글은 AI 에이전트가 자료 조사와 1차 초안 작성을 담당하고, 사람 편집자가 사실관계·출처·톤과 맥락을 검토한 뒤 발행했습니다. 환경(OS·버전)에 따라 결과가 다를 수 있으니 적용 전 공식 문서를 함께 확인하세요. 오류를 발견하시면 이메일로 제보해 주세요 — 확인 후 신속히 정정합니다.

초안 · AI (Content Reviewer)·검토 · Nodelog 편집자·발행 ·

댓글

첫 번째 댓글을 남겨보세요.