본문 바로가기
Script/Javascript

Error: listen EADDRINUSE: address already in use :::3000 해결, 3000번 포트 잡고 있는 프로세스 찾아서 끄는 법 (Node 26)

by 애플 로그 2026. 10. 18.
반응형

Node EADDRINUSE 3000번 포트 오류와 lsof로 PID 찾아 kill 한 터미널 출력

이 오류는 3000번 포트를 이미 다른 프로세스가 쓰고 있어서 새 서버가 자리를 못 잡았다는 뜻이에요. lsof -nP -iTCP:3000 -sTCP:LISTEN 으로 그 프로세스의 PID를 찾아 kill 하면 바로 풀려요.

대부분은 예전에 띄운 서버가 아직 살아 있는 경우예요. 특히 터미널에서 Ctrl+C 대신 Ctrl+Z를 누르면 서버가 멈춘 채로 포트를 계속 쥐고 있어서 같은 오류가 나요. 이 경우도 직접 재현해 봤어요.

테스트 환경: macOS 26.6.2, Node.js v26.5.0

오류가 나는 순간을 그대로 재현해 봤어요

아래처럼 3000번 포트로 뜨는 아주 짧은 서버 파일을 만들었어요.

const http = require('http');
const server = http.createServer((req, res) => res.end('ok'));
server.listen(3000, () => console.log('listening on 3000, pid', process.pid));

첫 번째 실행은 정상으로 떴고, 이 상태에서 터미널을 하나 더 열어 같은 파일을 한 번 더 실행했어요. 두 번째 실행에서 나온 출력이에요.

node:events:487
      throw er; // Unhandled 'error' event
      ^

Error: listen EADDRINUSE: address already in use :::3000
    at Server.setupListenHandle [as _listen2] (node:net:2167:16)
    at listenInCluster (node:net:2224:12)
    at Server.listen (node:net:2344:7)
    at Object.<anonymous> (/private/tmp/ea/server.js:3:8)
    at Module._compile (node:internal/modules/cjs/loader:1934:14)
    at Object..js (node:internal/modules/cjs/loader:2074:10)
    at Module.load (node:internal/modules/cjs/loader:1656:32)
    at Module._load (node:internal/modules/cjs/loader:1448:12)
    at wrapModuleLoad (node:internal/modules/cjs/loader:261:19)
    at Module.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:154:5)
Emitted 'error' event on Server instance at:
    at emitErrorNT (node:net:2203:8)
    at process.processTicksAndRejections (node:internal/process/task_queues:90:21) {
  code: 'EADDRINUSE',
  errno: -48,
  syscall: 'listen',
  address: '::',
  port: 3000
}

Node.js v26.5.0

맨 끝 :::3000 은 IPv6 주소 표기예요. 앞의 :: 는 모든 주소라는 뜻이고 마지막 3000이 포트 번호예요. 오류 코드 EADDRINUSE는 Address in use, 주소가 이미 쓰이고 있다는 말이에요.

누가 3000번을 쥐고 있는지 찾기

맥과 리눅스에서는 lsof로 바로 찾을 수 있어요. -nP 를 붙여야 포트가 숫자로 나와요. 이걸 빼면 3000 대신 hbci 같은 서비스 이름으로 표시돼서 헷갈려요(실제로 그렇게 나왔어요).

$ lsof -nP -iTCP:3000 -sTCP:LISTEN
COMMAND   PID       USER   FD   TYPE             DEVICE SIZE/OFF NODE NAME
node    33967 kimyongnam   12u  IPv6 0x548d36f809d66d8e      0t0  TCP *:3000 (LISTEN)

COMMAND가 node이고 PID가 33967이에요. 이 번호를 kill 하면 돼요. PID만 뽑고 싶으면 lsof -ti :3000 처럼 -t 옵션을 쓰면 숫자만 나와요.

프로세스를 끄고 다시 띄우기

$ kill 33967
$ lsof -nP -iTCP:3000 -sTCP:LISTEN
(출력 없음, exit=1)
$ node server.js
listening on 3000, pid 34058

kill 뒤에 lsof를 다시 치면 아무것도 안 나오고, 그 상태에서 서버를 띄우니 정상으로 올라왔어요. 기본 kill로 안 꺼질 때만 kill -9 를 쓰세요. -9는 정리 작업 없이 바로 끊어 버리기 때문에 처음부터 쓰지 않는 편이 낫습니다.

윈도우는 lsof가 없어서 명령이 달라요. 이 PC에서 직접 돌려 보지는 못했고 마이크로소프트 공식 문서 기준으로 적어요. netstat -ano 의 -o 옵션이 연결마다 PID를 보여 주고, taskkill /pid 번호 로 그 프로세스를 끝내요. 강제로 끝낼 때는 /f 를 붙여요.

netstat -ano | findstr :3000
taskkill /pid 12345 /f

Ctrl+Z로 멈춘 서버가 포트를 쥐고 있는 경우

터미널에서 서버를 끌 때 Ctrl+C 대신 Ctrl+Z를 누르면 프로세스가 종료되지 않고 일시정지 상태로 남아요. 이걸 확인하려고 실행 중인 서버를 kill -STOP 으로 멈춰 놓고 확인해 봤어요.

$ lsof -nP -iTCP:3000 -sTCP:LISTEN
COMMAND   PID       USER   FD   TYPE             DEVICE SIZE/OFF NODE NAME
node    33967 kimyongnam   12u  IPv6 0x548d36f809d66d8e      0t0  TCP *:3000 (LISTEN)
$ curl -m 2 localhost:3000
curl exit=28

서버는 멈춰서 응답을 못 하는데(curl이 2초 제한에 걸려 종료 코드 28), 포트는 여전히 LISTEN 상태로 잡혀 있어요. 그래서 새 서버를 띄우면 EADDRINUSE가 나요. 같은 터미널이라면 jobs 로 멈춘 작업을 확인하고 fg 로 되살린 뒤 Ctrl+C로 끄면 돼요.

다음부터 안 겪으려면

포트가 차 있을 때 다른 포트로 넘어가게 만들 수도 있어요. Node.js 공식 문서(net 모듈, server.listen)에도 EADDRINUSE를 error 이벤트로 받아 처리하는 예제가 있고, 첫 listen이 오류로 끝난 경우에는 listen을 다시 호출할 수 있다고 나와 있어요. 아래는 3001번으로 넘어가게 바꾼 코드와 실제 출력이에요.

const http = require('http');
const server = http.createServer((req, res) => res.end('ok'));
server.on('error', (err) => {
  if (err.code === 'EADDRINUSE') {
    console.log(`3000번 포트 사용 중, 3001번으로 다시 시도`);
    server.listen(3001);
  } else throw err;
});
server.listen(3000, () => console.log('listening on', server.address().port));

$ node server2.js
3000번 포트 사용 중, 3001번으로 다시 시도
listening on 3001

다만 프런트엔드 개발 서버처럼 주소가 고정돼야 하는 경우에는 포트가 몰래 바뀌는 게 더 헷갈릴 수 있어요. 그럴 때는 포트를 넘기지 말고, 남은 프로세스를 찾아 끄는 습관이 낫습니다.

상황 확인 명령 해결
예전에 띄운 서버가 살아 있음 lsof -nP -iTCP:3000 -sTCP:LISTEN kill PID
Ctrl+Z로 멈춘 서버 jobs fg 후 Ctrl+C
윈도우 netstat -ano 와 findstr :3000 taskkill /pid PID /f
포트를 자동으로 넘기고 싶음 코드에서 error 이벤트 처리 다른 포트로 listen

관련 글: 서버 포트가 열려있는지 확인 방법 정리

이 글의 코드와 명령어는 직접 실행해 확인했고, 정리에는 AI 도구를 활용했습니다.

출처: Node.js 공식 문서 net 모듈 server.listen, Microsoft Learn netstat, taskkill 명령 문서 (2026년 10월 18일 확인)

댓글