홈/인프라/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초 진단 런북
목차
  1. 'connection refused'는 "무응답"이 아니라 "적극적 거부"다
  2. 30초 분기 런북: 4단계 의사결정 트리
  3. 핵심 함정: 127.0.0.1 바인드 vs 0.0.0.0 바인드
  4. 같은 에러, 다른 얼굴: 언어/툴별 메시지 매핑
  5. Docker·클라우드 특화 함정
  6. 결론: 4단계 체크리스트 카드
  7. 자주 묻는 질문 (FAQ)

connection refused / ECONNREFUSED 127.0.0.1 30초 분기 런북

이 글이 맞는 에러: Error: connect ECONNREFUSED 127.0.0.1:5432, connect ECONNREFUSED ::1:3000, curl: (7) Failed to connect to localhost port 8080 after 0 ms: Could not connect to server, dial tcp 127.0.0.1:6379: connect: connection refused. 모두 "그 주소·포트에서 듣는 프로세스가 없다"는 뜻입니다.

원인확인 명령해결
서버 프로세스가 안 떠 있음sudo ss -tlnp에 해당 포트가 없음서비스 기동, 기동 실패 로그 확인
127.0.0.1에만 바인드(원격·컨테이너 밖에서 접속)ss 출력이 127.0.0.1:<포트>앱을 0.0.0.0(또는 해당 IP)에 바인드
localhost가 ::1로 풀리는데 서버는 IPv4만에러 주소가 ::1클라이언트에 127.0.0.1 명시, 또는 서버를 ::에도 바인드
컨테이너 안에서 localhost로 다른 서비스 접속접속 주소, docker ps 포트 매핑compose 서비스 이름 또는 host.docker.internal 사용
방화벽 REJECT 규칙sudo nft list ruleset / sudo iptables -L -n해당 포트 허용

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

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

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

예외가 하나 있습니다. 방화벽이 패킷을 버리지(DROP) 않고 REJECT 규칙으로 응답하면 ICMP port-unreachable(기본값)이나 TCP RST가 돌아와 클라이언트에는 똑같이 refused로 보입니다(iptables REJECT). 서버가 확실히 떠 있는데 refused라면 방화벽 규칙도 확인하세요.

먼저 옆 에러들과 구분하자

에러 신호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 직접 사용

위험도 라벨 복붙 명령표

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

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

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

현업에서 "로컬에선 되는데 컨테이너/원격에선 refused"가 나오면 가장 먼저 확인할 것이 이것입니다. 직접 재현해 봅시다.

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  5    127.0.0.1:8000  0.0.0.0:*  users:(("python",pid=...))

# B의 경우
LISTEN 0  5    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()은 host를 지정하지 않으면 127.0.0.1에서만 듣고(Flask 문서), 다른 dev 서버도 루프백이 기본인 경우가 많습니다. "내 노트북에선 멀쩡한데 EC2/도커에서만 거부"가 뜨면 코드부터 보지 말고 ss -tlnp로 바인드 주소부터 확인하세요. 저는 이걸로 날린 시간이 며칠치는 됩니다.

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

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

도구에러 메시지
curl 8.xcurl: (7) Failed to connect to localhost port 8080 after 0 ms: Could not connect to server (-v로 보면 connect to 127.0.0.1 port 8080 ... failed: Connection refused)
curl 구버전curl: (7) Failed to connect to localhost port 8080: Connection refused
NodeError: connect ECONNREFUSED 127.0.0.1:5432
Node(localhost로 접속)AggregateError [ECONNREFUSED] 안에 connect ECONNREFUSED ::1:5432, connect ECONNREFUSED 127.0.0.1:5432
Godial tcp 127.0.0.1:6379: connect: connection refused
psqlpsql: error: connection to server at "127.0.0.1", port 5432 failed: Connection refused (구버전: could 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.
  • Node에서 ::1이 찍히는 경우: Node 17부터 localhost를 OS가 돌려준 순서대로 쓰기 때문에(verbatim) ::1이 먼저 나올 수 있습니다(dns 문서). Node 20부터는 autoSelectFamily 기본값이 true라 IPv6·IPv4 주소를 차례로 시도하고, 모두 실패하면 AggregateError로 묶어 보여 줍니다(net 문서). 2026년 10월 기준 LTS는 Node 24(Active)와 22(Maintenance)이고, Node 20은 2026-04-30에 지원이 끝났습니다(Node.js 릴리스 일정).
  • 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을 쓰세요. Docker Desktop(Mac/Windows)은 자동으로 풀어 주고, 리눅스 Docker Engine은 --add-host=host.docker.internal:host-gateway(compose는 extra_hosts)를 추가해야 합니다(Docker 문서). 같은 compose의 다른 컨테이너라면 서비스 이름(예: db:5432)으로 접속합니다.

  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로 실제 리스닝 주소부터 확인하세요.

확인 정보
✦ ✦ ✦
출처·검증 안내

글마다 출처와 확인 범위가 다릅니다. 아래에 확인일·확인 범위·출처가 기록된 글만 그 범위에서 확인된 것입니다. 기록이 없는 글은 적용 전 사용 중인 버전의 공식 문서로 직접 확인하고, 오류는 이메일로 알려주세요. 편집 정책 · 이메일로 제보

이 글에는 사람이 확인한 날짜·범위 기록이 아직 없습니다.

발행 · ·업데이트 ·

댓글

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