ECONNREFUSED, 연결 거부, localhost | 서버는 켰는데 fetch만 실패하면?
ECONNREFUSED는 코드가 틀린 게 아니라 그 주소와 포트에 수신 중인 프로세스가 없어서 커넥션이 거절된 줄입니다. 서버 창이 열려 있어도 포트가 다르거나 listen 전에 fetch가 나가면 납니다. Ready 한 줄과 fetch URL, IPv6 localhost, 카카오 콜백 포트. Node.js, Next.js, 한국 1인 개발자 기준. 2026년 9월 Node.js 에러·MDN Fetch 문서.
거절은 그 포트에 수신자가 없다는 뜻입니다. Node.js 문서는 ECONNREFUSED를 대상이 연결을 적극적으로 거부했을 때 나는 시스템 에러로 적습니다. 방화벽이 조용히 버리는 타임아웃과 달리, 거절은 "그 자리에 아무도 안 앉아 있다"는 빠른 응답이에요.
그래서 서버 프로세스가 떠 있는 것과, 지금 fetch가 두드리는 주소가 같은지는 다른 질문입니다. 창 제목에 Next.js가 보여도 실제 listen은 3000이 아니라 3001일 수 있고, API 라우트는 안 떠 있는데 페이지만 떠 있을 수도 있습니다.
EADDRINUSE와 자리도 다릅니다. 포트가 이미 점유면 새 서버가 못 앉고, 거절은 앉은 사람이 없는 쪽에 fetch가 간 경우입니다. 포트가 안 죽어서 서버가 안 뜨는 줄은 EADDRINUSE에서 다룹니다. 여기서는 이미 보낸 요청이 빈 자리를 두드린 줄만 봅니다.
먼저 기억할 것 | 에러에 찍힌 호스트와 포트 숫자를 그대로 복사하세요. 코드의 기본값 3000과 실제 서버 안내 한 줄이 다른 경우가 대부분입니다.
서버 창이 켜져 있어도 fetch가 다른 포트로 가면 거절이 난다
호스트와 포트를 한 표로 맞추기
호스트와 포트 숫자를 한 줄로 대조하세요. 클라이언트가 부르는 URL과 서버가 listen한 자리가 같아야 거절이 사라집니다.
칸
증상
먼저 할 일
포트 불일치
서버는 3001, fetch는 3000
터미널 Ready 한 줄과 fetch URL 대조
서버 미기동
페이지는 뜨고 API만 거절
npm run dev가 같은 저장소인지
프록시 누락
브라우저는 /api, 서버는 다른 포트
next.config rewrites 또는 같은 출처
도커 포트
컨테이너 안 3000, 호스트 미매핑
docker ps 포트 열 확인
디비 거절
앱은 뜨고 프리즈마만 거절
P1001과 호스트 칸이 다름
다섯 번째 칸은 디비입니다. 프리즈마가 호스트까지 TCP를 못 열면 P1001이 나고, 앱 fetch가 빈 API 포트를 치면 ECONNREFUSED입니다. 디비 쪽은 P1001을 보세요.
같은 출처로 묶으면 브라우저 fetch가 3001을 직접 안 두드립니다. Next.js rewrites로 /api를 뒤쪽 서버에 넘기면, 화면과 API가 같은 호스트처럼 보입니다. CORS와 자리가 겹치면 CORS 프리플라이트도 함께 보세요. CORS는 응답을 읽지 못하게 막는 줄이고, 거절은 연결 자체가 안 열린 줄입니다.
서버 포트와 fetch URL을 같이 찍기
# 터미널 Ready 한 줄에서 실제 포트 확인
# Local: http://localhost:3001
# 그 포트에 수신자가 있는지
lsof -i :3001
# 클라이언트가 두드리는 주소가 같은지
node -e "fetch('http://127.0.0.1:3001/api/health').then(r=>console.log(r.status)).catch(e=>console.error(e.cause||e))"
127.0.0.1과 localhost가 갈리는 경우
localhost가 IPv6만 가리키면 거절이 납니다. 맥에서 localhost는 ::1로 먼저 붙는 경우가 있고, 서버는 0.0.0.0의 IPv4만 듣고 있으면 거절이 떨어집니다.
에러 메시지에 ::1:3000이 찍히면 IPv6로 간 겁니다. 서버 listen 로그가 0.0.0.0:3000이면 자리가 다릅니다. 이 때는 fetch URL을 127.0.0.1로 고정하거나, 서버가 IPv6도 듣게 맞춥니다.
환경 변수에 박힌 기본값도 자주 갈립니다. .env의 NEXT_PUBLIC_API_URL=http://localhost:3000을 빌드 시점에 박아 두고, 개발 서버는 3001로 올라간 경우죠. 폴더마다 값이 다르면 direnv 폴더 환경으로 저장소 루트 값을 고정하는 편이 낫습니다.
카카오와 토스는 브라우저나 외부 서버가 다른 주소를 두드립니다. 로컬 넥스트는 떠 있는데, 카카오 콘솔에 적은 Redirect URI가 다른 포트면 콜백이 거절되거나 콘솔 에러로 떨어집니다.
카카오 로그인은 브라우저가 카카오 서버를 거쳐 내 콜백 URL로 돌아옵니다. 그 URL의 호스트와 포트가 지금 떠 있는 개발 서버와 같아야 합니다. 3000으로 콘솔에 등록해 두고 실제 서버는 3001이면, 돌아온 요청이 빈 포트를 치거나 엉뚱한 페이지로 갑니다.
토스 웹훅은 토스 서버가 내 공개 URL을 때립니다. 로컬호스트는 토스가 닿지 않으니, 로컬에서 웹훅을 시험하려면 터널이 있어야 합니다. 터널이 꺼진 채로 토스 테스트 웹훅을 보내면 상대 쪽 로그에 연결 실패가 남고, 내 터미널엔 아무 요청이 안 들어옵니다. 이건 내 코드가 거절한 게 아니라, 바깥에서 내 로컬에 닿지 못한 겁니다.
버셀 프리뷰에 올리면 주소가 매일 바뀝니다. 카카오 콘솔 Redirect URI와 토스 웹훅 URL을 프리뷰 도메인에 맞춰 넣지 않으면, 로컬에선 되고 프리뷰만 거절 또는 404가 납니다. 같은 출처로 묶는 프록시와 서버 액션은 프록시 매처 글도 참고하세요.
거절과 CORS를 섞지 말 것 | 콘솔에 CORS 빨간 줄이 있으면 연결은 된 겁니다. ECONNREFUSED는 연결 전에 끝납니다. 네트워크 탭에서 상태 코드가 없고 failed만 있으면 거절 쪽을 먼저 보세요.
디비 거절과 앱 fetch 거절은 다른 줄
디비 TCP가 안 열린 줄과 앱 API가 빈 포트를 친 줄은 로그 칸이 다릅니다. 프리즈마 로그에 P1001이 있으면 DATABASE_URL 호스트를 보고, 브라우저 콘솔에 ECONNREFUSED가 있으면 페이지가 부른 API URL을 봅니다.
모듈을 못 찾아 서버가 기동 전에 죽은 경우도 거절로 이어집니다. 서버 파일이 의존성을 못 읽어 listen 전에 끝나면, fetch는 빈 포트를 칩니다. 그 시작은 Cannot find module입니다. 서버 로그 맨 위 줄을 먼저 읽으세요.
창이 켜진 것과 listen 포트가 같은지는 별개입니다. 터미널 Ready 한 줄의 포트와 fetch URL 숫자를 대조하세요. 3000으로 불렀는데 서버는 3001에 앉아 있으면 거절이 납니다.
로컬은 되고 카카오 콜백만 실패합니다.
카카오 콘솔 Redirect URI의 호스트와 포트가 지금 떠 있는 개발 서버와 같은지 보세요. 콘솔은 3000, 실제 서버는 3001이면 돌아온 요청이 빈 자리를 칩니다.
localhost와 127.0.0.1 중 무엇을 써야 하나요?
거절이 IPv6(::1)로 찍히면 127.0.0.1로 한 번 재현해 보세요. 서버가 IPv4만 듣고 localhost가 ::1로 붙는 맥 환경에서 자주 납니다. 둘 다 되는 쪽으로 환경 변수를 고정하면 됩니다.
CORS 에러와 무엇이 다른가요?
CORS는 연결은 되고 브라우저가 응답 읽기를 막는 줄입니다. ECONNREFUSED는 연결 전에 끝납니다. 네트워크 탭에 상태 코드가 없고 failed만 있으면 거절입니다.
도커로 올리면 갑자기 거절됩니다.
컨테이너 안 포트와 호스트에 연 포트가 다를 수 있습니다. docker ps의 포트 열을 보고, 클라이언트가 호스트 쪽 숫자를 부르는지 확인하세요. 컨테이너 이름 그대로 localhost에 붙이면 호스트에 수신자가 없습니다.
프리즈마 P1001과 같이 보이면 무엇을 먼저 보나요?
로그가 프리즈마 쪽이면 DATABASE_URL 호스트를, 브라우저 fetch면 API URL을 봅니다. 둘 다 거절처럼 보여도 칸이 다릅니다. 디비는 P1001 글을, 앱 API는 이 글의 포트 대조를 따르세요.
ECONNREFUSED는 그 주소와 포트에 수신자가 없다는 뜻입니다. 터미널 Ready 한 줄과 fetch URL을 맞추고, localhost가 IPv6로만 붙는지 본 다음, 카카오 콘솔 포트까지 같은지 보면 대부분 풀립니다. 관련 글: 포트 점유, CORS, P1001.