리눅스 nohup 명령어 사용법 - SSH 끊겨도 작업이 계속 돌게 하기
이 명령어를 언제 쓰는가
몇 시간 걸리는 데이터 마이그레이션이나 대용량 덤프를 돌리는데, SSH 세션이 끊기면 작업도 같이 죽는다. 이를 막는 것이 nohup이다.
터미널이 닫히면 커널은 그 터미널에 붙어 있던 프로세스들에게 SIGHUP(hang up)을 보내고, 대부분의 프로그램은 이 시그널을 받으면 종료한다. nohup은 명령을 실행하기 전에 SIGHUP을 무시하도록 설정해서 이 연결 고리를 끊는다. 사무실 회선이 불안정하거나 VPN이 자주 끊기는 환경에서 특히 값어치를 한다.
기본 형식
nohup 명령 [인자...] > 로그파일 2>&1 &
네 부분이 다 필요하다. nohup이 SIGHUP을 막고, > 로그파일이 표준 출력을 파일로 보내고, 2>&1이 에러까지 같은 파일로 합치고, 마지막 &가 백그라운드로 보낸다. 하나라도 빠지면 뒤에서 설명할 문제가 생긴다.
자주 쓰는 조합
| 조합 | 의미 | 예시 |
|---|---|---|
| > 파일 2>&1 | 표준 출력과 표준 에러를 한 파일에 | nohup ./job.sh > job.log 2>&1 & |
| < /dev/null | 표준 입력을 막는다. 백그라운드 정지 방지 | nohup ./job.sh < /dev/null > job.log 2>&1 & |
| echo $! | 방금 띄운 백그라운드 프로세스의 PID | echo $! > /var/run/job.pid |
| >> 파일 | 덮어쓰지 않고 이어쓰기 | nohup ./job.sh >> job.log 2>&1 & |
| > /dev/null | 출력을 버린다. 애플리케이션이 자체 로그를 쓸 때 | nohup ./app > /dev/null 2>&1 & |
실전 예제
1) 장시간 배치를 로그와 함께 돌린다. 가장 기본이 되는 형태다. 로그 경로는 홈이 아니라 쓰기 권한이 확실한 곳으로 지정한다.
nohup ./migrate_users.sh > /var/log/migrate_users.log 2>&1 &
[1] 24513
터미널에는 잡 번호와 PID만 찍힌다. nohup의 안내 메시지는 표준 에러로 나가는데 2>&1로 이미 로그 파일에 합쳐졌기 때문이다. 리다이렉트를 어떻게 주느냐에 따라 GNU coreutils nohup이 내는 메시지가 달라진다. 아무것도 안 주면 nohup: ignoring input and appending output to 'nohup.out', > job.log만 주고 2>&1을 빼면 nohup: redirecting stderr to stdout이 터미널에 남는다.
2) PID를 남겨서 나중에 상태를 확인하고 중단할 수 있게 한다. $!는 직전 백그라운드 프로세스의 PID다.
nohup ./migrate_users.sh > /var/log/migrate_users.log 2>&1 &
echo $! > /var/run/migrate_users.pid
3) 진행 상황을 본다. 세션을 다시 붙였을 때 로그로 상태를 확인하고, 프로세스가 살아 있는지는 ps로 본다.
tail -f /var/log/migrate_users.log
ps -p $(cat /var/run/migrate_users.pid) -o pid,etime,rss,cmd
4) 표준 입력을 확실히 막는다. 프로그램이 어쩌다 입력을 읽으려 하면 백그라운드 프로세스는 SIGTTIN을 받고 멈춰 버린다. 원인을 찾기 아주 어려운 증상이므로 습관적으로 붙이는 편이 낫다.
nohup mysqldump --defaults-file=/etc/my.cnf.d/dump.cnf shopdb \
< /dev/null > /backup/shopdb.sql 2> /var/log/dump.err &
5) 이미 띄운 포그라운드 작업을 뒤늦게 살린다. nohup을 깜빡했을 때의 구제책이다. Ctrl+Z로 멈추고, 백그라운드로 보내고, 셸의 잡 목록에서 떼어낸다.
# Ctrl+Z 를 누른 뒤
bg
disown -h %1
함정과 주의점
리다이렉트를 빼먹으면 nohup.out이 쌓인다. 출력 경로를 지정하지 않으면 nohup은 현재 디렉터리의 nohup.out에 이어쓰기를 한다. 쓰기 권한이 없으면 $HOME/nohup.out으로 간다. 이 파일이 수십 GB까지 자라 디스크를 채우는 사고가 흔하다. 그리고 여러 작업을 같은 디렉터리에서 띄우면 로그가 한 파일에 뒤섞여 아무 쓸모가 없어진다. 항상 명시적으로 리다이렉트한다.
&만 붙이고 nohup을 빼면 의미가 없다. 백그라운드로 보내는 것과 SIGHUP을 막는 것은 별개다. ./job.sh &만 하면 로그아웃할 때 그대로 죽는다. 반대로 nohup을 쓰고 &를 빼면 포그라운드에서 돌아 터미널이 묶인다. 둘 다 필요하다.
nohup은 SIGHUP만 막는다. systemd의 로그인 세션 정리 설정(/etc/systemd/logind.conf의 KillUserProcesses)이 yes면, 마지막 세션이 끝날 때 사용자 프로세스가 통째로 정리되어 nohup도 소용없다. systemd 230(2016)에서 이 기본값이 yes로 바뀌었다가 반발이 커서 주요 배포판이 다시 no로 되돌린 이력이 있다. 지금 대부분의 배포판은 no가 기본이지만, 사내 표준 이미지에서 바꿔 놓은 경우가 있으니 중요한 작업 전에는 확인한다. 확실하게 세션에서 분리하려면 setsid나 systemd-run --user --scope를 쓴다.
진짜 서비스는 nohup으로 띄우지 마라. nohup에는 자동 재시작도, 리소스 제한도, 표준화된 로그 수집도, 부팅 시 자동 실행도 없다. 계속 떠 있어야 하는 프로세스라면 systemd 유닛으로 등록하는 것이 정답이다. nohup은 일회성 장시간 작업을 위한 도구다.
함께 보면 좋은 명령어
- tmux / screen — 세션 자체를 서버에 남겨 둔다. 나중에 다시 붙어 화면을 그대로 볼 수 있어서 대화형 작업에는 nohup보다 낫다.
- disown — 이미 실행 중인 잡을 셸의 관리 목록에서 떼어낸다. nohup을 빼먹었을 때의 대안이다.
- systemd-run — 일회성 명령을 systemd 스코프로 띄워 세션 종료와 완전히 무관하게 만든다.