Develop

PM2로 Node.js 앱을 서버에서 관리하는 방법 — 프로세스 자동 재시작과 startup 설정

nohup으로 Node.js를 띄우다가 PM2로 전환하면 프로세스 자동 재시작, 서버 재부팅 후 자동 실행, 로그 관리까지 한 번에 해결된다. 설치부터 ecosystem.config.js 설정까지 정리했다.

PM2Node.js프로세스관리infra서버
pm2 list 명령 실행 결과 화면 — Node.js 앱 이름, online 상태, CPU와 메모리 사용량이 테이블로 표시됨
  • ·PM2: 2013년 출시, Node.js 프로세스 관리 사실상 표준 도구
  • ·pm2 startup: OS 부팅 시 자동 실행을 위한 systemd 스크립트 자동 생성
  • ·cluster 모드: CPU 코어 수만큼 프로세스를 포크해 트래픽 분산
  • ·pm2 save: 현재 실행 중인 프로세스 목록을 저장해 재부팅 후 복원
리눅스 서버에 처음 백엔드 앱을 배포했을 때, `nohup`으로 띄워놓고 뿌듯해하던 기억이 납니다. 하지만 다음 날 아침 서비스 접속이 안 되어서 부랴부랴 서버를 확인해보니, 사소한 예외 하나를 잡아두지 않아 프로세스가 통째로 다운되어 있더군요. PM2를 알게 되어 마이그레이션한 이후로는 이러한 두려움이 말끔히 해소되었습니다. 프로세스가 종료되어도 0.1초 만에 살아나고, `ecosystem.config.js` 파일 하나만 공유해두면 팀 내 다른 서버에도 동일한 실행 스펙을 그대로 구축할 수 있어 프로젝트 배포 관리가 놀라울 정도로 든든해졌습니다.

1. Node.js 프로세스 관리자 PM2 소개

서버 환경에서 Node.js 프로세스 관리를 위해 PM2를 채택하는 이유

로컬 PC에서 Node.js 백엔드를 개발할 때는 터미널에 node app.js를 실행하는 것으로 충분하지만, 실제 배포된 리눅스 서버에서는 그렇게 대충 올려둘 수 없습니다. 서버 컴퓨터는 언제든 시스템 크래시나 전원 리부팅 등 물리적인 이상 상황을 겪을 수 있고, 애플리케이션 내부에서 처리되지 않은 예외(Uncaught Exception)가 터져 프로세스가 비정상 종료되는 현상도 비일비재합니다. 이러한 돌발 장애가 터질 때마다 밤낮없이 수동으로 서버에 SSH 원격 접속해서 다시 구동할 수는 없는 노릇이죠. 프로세스 매니저인 PM2는 이러한 운영 장애를 해결해 주는 사실상의 표준 솔루션입니다. PM2는 백엔드 프로세스가 예기치 못한 에러로 뻗어 버리면 1초도 안 되는 찰나에 이를 감지하고 프로세스를 즉시 자동으로 되살려줍니다. 또한, 싱글 스레드로 제한되어 멀티 코어 CPU 자원을 온전히 활용하지 못하는 Node.js의 한계를 극복할 수 있도록 내장 클러스터(Cluster) 모드를 지원하여, 별도의 소스코드 수정 없이도 코어 개수만큼 자동으로 프로세스를 늘리고 로드 밸런싱까지 구성해 주는 매우 유용한 인프라 도구입니다.

nohup 데몬 실행과 PM2 매니저의 안정성 차이점

배포 초기 단계에서 흔히 저지르는 실수 중 하나가 백그라운드로 백엔드를 유지하기 위해 nohup node app.js & 명령을 실행하고 넘어가는 일입니다. 물론 이 방식도 SSH 세션이 끊겨도 터미널 백그라운드에서 백엔드를 가동해 주긴 하지만, 딱 거기까지일 뿐이죠. 메모리 누수가 발생하거나 예상치 못한 에러로 세션이 뻗었을 때 이를 감시하고 자동으로 살려주는 부활 장치가 없습니다. 게다가 로그도 nohup.out이라는 단일 텍스트 파일에 무제한으로 누적되어, 운영 기간이 한두 달만 넘어가도 파일 크기가 수십 GB로 비대해져서 서버 디스크가 가득 차 서버 전체가 마비되는 심각한 장애로 이어지기 십상입니다. 반면 PM2는 가동 중인 서비스의 CPU 점유율, 메모리 소모량을 실시간 GUI 테이블로 모니터링할 수 있고, 프로세스가 재시작된 횟수까지 pm2 list 상에서 투명하게 추적할 수 있어 트러블슈팅이 훨씬 빨라집니다. 서버의 전반적인 가용성과 안정성을 고려한다면 테스트 단계를 제외한 프로덕션 배포 시점에는 두말할 필요 없이 PM2를 사용하는 것이 올바른 선택입니다.

2. PM2를 통한 애플리케이션 무중단 제어

명령어 인터페이스(CLI)로 PM2 앱 등록 및 관리하기

PM2의 시작은 아주 간단합니다. NPM 글로벌 옵션(npm install -g pm2)으로 설치를 진행한 뒤, pm2 start app.js --name 내앱이름 명령어로 앱을 백그라운드 데몬으로 바로 등록할 수 있습니다. 이때 --name 파라미터로 명시적인 네이밍을 해 주는 습관이 중요한데, 나중에 재시작이나 로그 추적을 진행할 때 포트나 번호 대신 pm2 restart 내앱이름처럼 직관적으로 제어할 수 있기 때문입니다. 실무에서 정말 자주 쓰게 되는 팁은 소스코드를 새로 배포하고 이를 적용하는 방식입니다. 서비스 중단 없이 실시간 반영을 원한다면 일반적인 pm2 restart 대신 pm2 reload 명령어를 써야 합니다. restart는 프로세스를 한 번 완전히 킬(Kill)하고 새로 띄우므로 아주 찰나의 순간 접속 장애가 생길 수 있지만, reload는 프로세스를 순차적으로 교체하는 무중단 롤링 배포를 수행하므로 가용성을 100%로 보장할 수 있습니다. 단, 앱이 비정상 종료되어 errored 상태로 빠질 때는 pm2 start를 반복 실행하지 말고 반드시 pm2 logs를 띄워 포트 충돌 여부 등 초기 구동 환경에 미비한 점이 없는지부터 확인해야 합니다.

# 설치
npm install -g pm2

# 앱 시작
pm2 start app.js --name my-app

# npm start 방식
pm2 start npm --name my-app -- start

# 상태 확인
pm2 list
pm2 logs my-app

# 재시작 / 중지 / 삭제
pm2 restart my-app
pm2 stop my-app
pm2 delete my-app

ecosystem.config.js 설정 파일로 PM2 배포 환경 일관성 유지하기

매번 pm2 start 명령에 옵션을 일일이 입력하는 대신 ecosystem.config.js 설정 파일로 관리하는 것이 정석입니다. 프로젝트 루트에 ecosystem.config.js 파일을 만들고 module.exports = { apps: [...] } 형태로 작성합니다. apps 배열 안에 각 앱의 설정 객체를 넣으면 됩니다. name, script, instances, exec_mode, env 같은 필드를 사용합니다. instances를 'max'로 설정하면 CPU 코어 수만큼 프로세스를 생성하고, exec_mode를 'cluster'로 설정하면 cluster 모드로 실행됩니다. env 객체에 환경변수를 설정하면 PM2가 앱을 시작할 때 해당 환경변수를 주입합니다. env_production처럼 환경별 설정을 분리할 수도 있습니다. pm2 start ecosystem.config.js로 실행하고, pm2 start ecosystem.config.js --env production으로 프로덕션 환경변수를 적용할 수 있습니다. 서버를 새로 셋업하거나 다른 서버로 이전할 때 ecosystem.config.js 파일 하나만 가져오면 동일한 설정을 그대로 재현할 수 있어서 인프라 관리가 단순해집니다. Git 저장소에 올려두면 팀 전체가 같은 PM2 설정을 공유할 수 있습니다. 다만 환경변수에 시크릿 값이 포함된다면 .gitignore로 제외하거나 서버에서만 관리해야 합니다.

// ecosystem.config.js
module.exports = {
  apps: [
    {
      name: 'my-app',
      script: 'app.js',
      instances: 'max',
      exec_mode: 'cluster',
      env: {
        NODE_ENV: 'development',
        PORT: 3000,
      },
      env_production: {
        NODE_ENV: 'production',
        PORT: 3000,
      },
    },
  ],
}

# 실행
pm2 start ecosystem.config.js
pm2 start ecosystem.config.js --env production

3. 인프라 운영 고도화: 자동 재부팅 및 로그 분할

서버 재부팅 시 앱이 살아나도록 pm2 startup 및 save 세팅하기

PM2가 아무리 유능하더라도 AWS EC2 서버 자체가 물리적인 업데이트나 전원 리부팅 등으로 재부팅되면 PM2 프로세스 매니저 자체도 종료될 수밖에 없습니다. 따라서 서버 부팅 시점에 OS의 systemd 서비스 데몬이 PM2를 자동으로 올려주도록 운영체제(OS) 수준의 서비스 등록을 해 두어야 합니다. 이 작업을 원클릭으로 처리해 주는 명령어가 바로 pm2 startup입니다. 이 명령을 수행하면 터미널 화면에 각 OS에 맞는 기나긴 셸 스크립트 명령 한 줄이 튀어나오는데, 해당 스크립트를 그대로 복사해서 다시 실행해 주면 시스템 부팅 서비스 등록이 끝납니다. 여기서 수많은 초보 인프라 관리자들이 겪는 황당한 함정 카드가 하나 있는데, 바로 pm2 save 명령의 누락입니다. startup 명령은 PM2라는 매니저 엔진 자체를 OS 자동 시작에 끼워놓는 역할만 할 뿐, 서버가 켜졌을 때 기존에 무슨 웹앱들을 돌리고 있었는지는 알려주지 않습니다. 그렇기 때문에 현재 가동 중인 백엔드 프로세스 리스트를 메모리에 동결 저장하기 위해 꼭 pm2 save를 실행해 주어야만 서버가 리부팅됐을 때 원상복구가 정상적으로 동작합니다.

pm2-logrotate 모듈로 로그 유실 없는 디스크 용량 관리하기

PM2는 등록된 앱들의 console.logconsole.error 메시지를 가로채 /home/유저/.pm2/logs 디렉토리 밑에 실시간 텍스트 파일로 고스란히 저장해 줍니다. 장애 디버깅 시에는 정말 생명줄과 같은 고마운 기능이지만, 트래픽이 몰리는 서비스라면 단 며칠 만에 수십 GB 단위로 로그 파일이 불어나 리눅스 디스크 용량 한계를 넘어 서버를 먹통으로 만드는 주요 원인이 됩니다. 그렇다고 로그를 아예 안 남길 수는 없으니, 파일 크기나 보존 기간에 따라 로그를 분할 저장하고 오래된 로그는 자동 삭제해 주는 로그 회전(Log Rotation) 처리를 해주어야 합니다. PM2 환경에서는 pm2 install pm2-logrotate 명령어로 전용 로그 모듈을 즉시 애드온 형태로 설치할 수 있습니다. 설치 후 pm2 set pm2-logrotate:max_size 10M으로 개별 로그의 용량 제한을 정해두고 pm2 set pm2-logrotate:retain 10으로 보존할 예비 파일 개수를 지정해 두면, 디스크 용량이 꽉 차는 장애 위협으로부터 완전히 해방될 수 있어 실무 인프라 세팅 단계에서 가장 먼저 적용해 두는 강력한 모듈입니다.

자주 묻는 질문

pm2 startup 등록 후 서버를 재부팅했는데도 Node.js 앱이 자동으로 기동되지 않습니다.+

pm2 startup 이후 현재 실행 중인 프로세스 목록을 디스크에 저장하는 pm2 save 명령어를 빠뜨리셨을 확률이 가장 높습니다. PM2 서비스가 켜지더라도 시작할 앱 리스트가 없다면 공회전만 하게 되므로, 앱을 띄워둔 상태에서 반드시 pm2 save를 실행하여 저장해 주시기 바랍니다.

개발 환경이나 싱글 코어 서버에서도 PM2의 cluster 모드를 적용해야 하나요?+

아닙니다. 코어가 1개뿐인 소형 서버나 개발 로컬 환경에서는 오히려 프로세스 간 전환 오버헤드가 더 클 수 있고 디버깅이 까다롭기 때문에 가볍게 fork 모드로 돌리는 것을 권장합니다. 멀티 코어가 지원되는 실서버 환경에서 비로소 instances: 'max' 옵션과 함께 cluster 모드의 진가가 드러납니다.

관련 글