Node.js 애플리케이션을 실행하려 할 때 Error: listen EADDRINUSE: address already in use :::3000 문구를 만나 당황해본 적이 있을 것이다.
다만 왜 이미 사용 중이라고 나오는지 정확한 원인을 몰라 컴퓨터를 재부팅하거나 포트 번호만 임의로 바꾸는 경우가 많다.
이번 글에서는 EADDRINUSE 에러가 발생하는 정확한 원인이 무엇인지, 포트를 점유 중인 프로세스를 찾아 안전하게 종료하는 방법, 그리고 코드 수준에서 백업 포트를 설정해 자동 대처하는 방안까지 완벽하게 정리해서 소개하겠다.
EADDRINUSE는 Error Address Already in Use의 줄임말로, 소켓 통신을 위해 지정한 IP 주소와 포트 번호 조합을 이미 다른 프로세스가 점유하고 있을 때 발생한다.
OS 레벨에서 하나의 네트워크 포트는 동시에 하나의 리스닝(Listening) 소켓만 바인딩할 수 있도록 제한하기 때문에 일어나는 지극히 정상적인 예외 처리다.
실무 개발 중 EADDRINUSE 에러가 일어나는 주된 상황은 다음과 같다.
- 백그라운드 프로세스 미종료: 지난 실행 시 Ctrl + Z로 터미널을 일시 중단하거나 비정상 종료하여 이전 Node.js 프로세스가 여전히 메모리에 남아 있는 경우
- nodemon 중복 실행: 코드 수정 시 자동 재시작을 도와주는 nodemon이 종종 기존 프로세스를 완전히 kill 하지 못한 상태에서 새 프로세스를 시작하는 경우
- 다른 서비스와의 포트 충돌: 3000번, 8000번, 8080번 등 자주 쓰이는 포트를 Docker 컨테이너, React dev server, 다른 백엔드 애플리케이션이 이미 사용 중인 경우
에러를 해결하려면 먼저 점유 중인 포트를 확인하고 해당 PID(Process ID)를 찾아 프로세스를 종료해야 한다.
사용하는 운영체제 환경에 맞춰 아래 명령어를 터미널에서 실행하면 즉시 해결할 수 있다.
| 운영체제 | 포트 점유 프로세스 확인 | 프로세스 강제 종료 |
|---|---|---|
| Linux / macOS | lsof -i :3000 | kill -9 [PID] |
| Windows (CMD / PowerShell) | netstat -ano | findstr :3000 | taskkill /F /PID [PID] |
Linux나 macOS 사용자의 경우 터미널에 lsof -i :3000을 입력하면 3000번 포트를 사용 중인 PID와 명령어를 한눈에 확인할 수 있다. 출력된 PID가 예를 들어 12345라면 kill -9 12345를 입력해 프로세스를 즉시 강제 종료하면 된다.
Windows 사용자는 명령 프롬프트(CMD)에서 netstat -ano | findstr :3000을 실행해 맨 우측에 있는 PID 숫자를 확인한 뒤 taskkill /F /PID 12345를 실행하면 즉시 포트가 해제된다.
단순히 터미널에서 프로세스를 죽이는 것은 일시적인 조치에 불과하다.
서버 코드 내에서 EADDRINUSE 에러 발생 시 자동으로 포트를 넘겨받아 재시도하거나 개발자에게 명확한 안내를 출력하도록 작성하는 것이 실무에서 안전하다.
✗ 잘못된 코드 (포트 고정 사용 및 에러 미처리)
const express = require('express');
const app = express();
const PORT = 3000;
// 포트 충돌 발생 시 처리 로직이 없어 Uncaught Error로 서버가 바로 다운됨
app.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`);
});위 코드는 3000번 포트가 이미 점유 중이면 Error: listen EADDRINUSE 발생과 함께 노드 프로세스가 즉시 비정상 종료된다.
✓ 올바른 코드 (EADDRINUSE 에러 감지 및 자동 fallback 처리)
const express = require('express');
const http = require('http');
const app = express();
const server = http.createServer(app);
let PORT = process.env.PORT || 3000;
function startServer(port) {
server.listen(port);
}
server.on('error', (error) => {
if (error.code === 'EADDRINUSE') {
console.warn(`[Warning] 포트 ${PORT}번이 이미 사용 중입니다. 다음 포트로 재시도합니다...`);
PORT += 1;
// 포트 번호를 1 증가시켜 다시 수신 시도
setTimeout(() => {
startServer(PORT);
}, 500);
} else {
console.error('서버 실행 중 알 수 없는 에러 발생:', error);
process.exit(1);
}
});
server.on('listening', () => {
console.log(`[Success] 서버가 성공적으로 시작되었습니다. (Port: ${PORT})`);
});
startServer(PORT);결과 / 출력값 (3000번 포트가 사용 중일 때)
[Warning] 포트 3000번이 이미 사용 중입니다. 다음 포트로 재시도합니다...
[Success] 서버가 성공적으로 시작되었습니다. (Port: 3001)
포트 충돌 문제를 해결할 때 많은 개발자가 범하는 실무적 오류들이 존재한다.
근본적인 원인을 파악하지 않고 땜빵식 대응을 계속하면 운영 환경에서 심각한 문제로 이어질 수 있다.
✗ 잘못된 대응
포트 충돌이 날 때마다 코드의 포트 번호를 3001, 3002, 3003으로 계속 변경하며 방치한다.
무작정 PID를 조회하지 않고 systemctl restart나 PC 재부팅에만 의존한다.
✓ 올바른 대응
어떤 서비스가 포트를 점유했는지 lsof나 netstat 명령어로 반드시 확인한다.
개발 시 nodemon이나 PM2 이용 시 프로세스가 정상 종료(SIGINT/SIGTERM)되도록 Graceful Shutdown 핸들러를 구축해 둔다.
Node.js EADDRINUSE 에러 해결은 안정적인 서버 구동과 개발 생산성 유지의 핵심이다. 작은 프로세스 관리 습관이 모여서 서비스의 높은 가용성을 만든다는 점을 잊지 말자. 이 글의 OS별 포트 조회 및 프로세스 종료 명령어를 참고해 개발 환경을 정돈하면, 더 이상 포트 충돌 문제로 시간을 허비하지 않는 쾌적한 개발 환경을 구축할 수 있을 것이다.