Views: 0
원격 서버에서 긴 작업을 돌리다 연결이 끊기면 프로세스가 함께 종료되는 일이 잦다. 이는 로그인 세션이 끊길 때 커널이 세션의 자식 프로세스에게 SIGHUP을 보내기 때문이다. nohup은 이 신호를 무시하도록 설정하고 표준 출력·오류를 파일로 돌려 안정적으로 작업을 지속시킨다. 이 글은 nohup의 동작 원리, 올바른 사용법, 로그 설계, 실패 사례의 원인과 해결, 그리고 setsid, disown, systemd-run 같은 실무 대안을 한 번에 정리한다.
목차
왜 SSH가 끊기면 프로세스가 죽는가
- 로그인 셸은 부모 세션의 프로세스 그룹을 관리한다.
- 세션이 종료되면 커널이 해당 그룹에 SIGHUP을 브로드캐스트한다.
- 많은 프로그램이 기본 처리로 SIGHUP에서 종료한다.
핵심은 “세션에 묶이지 않게 만들고(HUP 무시), 입출력 스트림을 터미널에서 떼는 것”이다.
nohup의 동작 원리
nohup은 실행 전 다음을 수행한다.
- SIGHUP 무시:
signal(SIGHUP, SIG_IGN) - 표준 출력/오류 재지정: 지정이 없다면
nohup.out로 보냄 - 표준 입력: 터미널로 남아있으면 예상치 못한 블로킹이 생길 수 있으므로
/dev/null로 바꾸는 습관이 안전하다.
기본 예시
nohup ./long_job.sh &
위처럼만 써도 동작하지만, 실무에서는 로그와 입력을 명시해 예측 가능하게 만든다.
실무에서 안전한 실행 패턴
다음 1줄을 템플릿처럼 외워두면 불상사를 줄인다.
nohup <cmd> </dev/null > job.out 2>&1 &
< /dev/null: 표준 입력을 닫아 대기 상태 방지> job.out 2>&1: 출력과 오류를 한 파일로 합쳐 분석 일관성 확보&: 백그라운드 실행.nohup만으로는 포그라운드에 남을 수 있다.
로그를 날짜별로 분리하고 싶다면:
TS=$(date +%F_%H%M%S)
nohup <cmd> </dev/null > logs/<name>.$TS.log 2>&1 &
nohup.out의 위치와 권한
- 파일을 지정하지 않으면 CWD에
nohup.out이 생성된다. CWD에 쓰기 권한이 없으면$HOME/nohup.out로 떨어질 수 있다. - 컨테이너·크론 환경처럼 CWD가 애매한 경우, 반드시 경로를 절대경로로 지정한다.
파이프라인과 nohup
파이프 양쪽 중 어느 쪽이 HUP을 받느냐에 따라 결과가 달라진다. 가장 쉬운 해법은 “전체 파이프라인을 셸 한 번으로 감싸고 그 셸을 nohup 처리”하는 것이다.
nohup bash -c 'producer | filter | consumer' </dev/null > pipe.log 2>&1 &
개별 커맨드에 nohup을 중첩하는 것보다 오류 원인을 줄인다.
중간에 nohup 없이 시작했다면: disown
작업을 이미 시작했고 로그도 잘 나오는데, 갑자기 연결을 끊어야 한다면 셸의 잡 제어로 세션 연결을 끊을 수 있다.
# 1) 현재 포그라운드 잡을 백그라운드로
^Z
bg %1
# 2) HUP 무시(선택)
trap '' HUP
# 3) 셸이 해당 잡과의 관계를 끊음
disown -h %1
-h는 HUP 무시 플래그를 남긴다. 다만 일부 프로그램은 이미 표준 출력을 터미널에 묶어둔 채 버퍼링하고 있을 수 있으니, 처음부터 리다이렉션과 함께 실행하는 편이 안정적이다.
세션에서 완전히 분리: setsid
세션 리더가 아닌 새 세션에서 실행해 SIGHUP·CONT 등 세션 신호를 차단한다. nohup과 궁합이 좋다.
setsid nohup <cmd> </dev/null > run.log 2>&1 &
setsid만 쓰면 HUP 무시는 보장되지 않으므로, 장시간 배치라면 nohup과 함께 사용한다.
systemd-run으로 “작은 서비스”처럼 돌리기
서버가 systemd라면 SSH와 무관한 독립 유닛으로 띄우는 것이 가장 견고하다. 로그도 저널로 수집되고, 재부팅 이후 정책도 설정할 수 있다.
# 현재 사용자 스코프로 실행
systemd-run --user --scope --unit=adhoc-backup bash -lc \
'./backup.sh </dev/null > ~/logs/backup.$(date +%F).log 2>&1'
# 데몬화(백그라운드 유지), 재시작 정책과 시간 제한 예시
systemd-run --unit=myjob --property=Restart=on-failure \
--property=RuntimeMaxSec=8h --collect bash -lc '<cmd>'
실행 후 상태와 로그 확인:
systemctl --user status adhoc-backup
journalctl --user -u adhoc-backup -f
루트 단위로 돌릴 땐 --user를 빼고, 접근 권한과 보안 정책을 점검한다.
크론·타이머와의 차이
- 크론은 기본적으로 메일로 출력 결과를 보낸다. 잡별로 명시적으로 리다이렉션하자.
- systemd-timer는 유닛과 세트로 관리되어 재현성과 관측성이 우수하다. ad-hoc 작업이 반복 패턴으로 굳어지면 타이머로 승격하는 전략이 관리 비용을 줄인다.
로그 설계와 회전
장시간 작업은 로그가 급격히 커질 수 있다.
- 파일 로그: logrotate 정책을 준비한다.
- 저널:
SystemMaxUse,RuntimeMaxUse등 용량 제한을 설정한다. - 실시간 추적:
tail -F job.out처럼 파일 회전에도 끊기지 않는 옵션을 사용한다.
리소스 한도와 ulimit
SSH가 끊겨도 작업이 계속되려면 자원 한도에도 걸리지 않아야 한다. 시작 전에 확인한다.
ulimit -a
# 특히 nofile, nproc, as, fsize
서비스화(systemd-run)할 땐 유닛 속성으로 한도를 명시한다.
systemd-run --unit=myjob -p LimitNOFILE=200000 -p TasksMax=infinity bash -lc '<cmd>'
신호와 종료 처리
nohup은 HUP만 무시하도록 설정한다. SIGTERM·SIGINT·SIGKILL은 여전히 유효하다. 종료 훅을 활용하려면 애플리케이션에서 신호 핸들러를 갖추거나, 래퍼 스크립트에 트랩을 둔다.
#!/usr/bin/env bash
set -euo pipefail
cleanup(){ echo "cleanup..."; }
trap cleanup TERM INT
<main logic>
실패 사례와 해결
- 작업이 멈춘 것처럼 보임: 표준 입력이 터미널에 남아 read 대기 중일 수 있다 →
< /dev/null필수. - 로그가 비어 있음: 버퍼링으로 즉시 쓰지 않는 프로그램일 수 있다 →
stdbuf -oL -eL로 줄단위 버퍼링 또는 프로그램 자체 옵션 사용.
nohup stdbuf -oL -eL <cmd> </dev/null > job.out 2>&1 &
- nohup인데도 종료됨: 래퍼 스크립트가 내부에서 새 프로세스를 만들어 세션 신호를 별도로 처리했을 수 있다 →
setsid를 병행하거나systemd-run으로 격리. - 권한 문제로
nohup.out이 엉뚱한 곳에 생김: 항상 절대경로 로그를 지정.
보안·운영 관점 체크리스트
- 실행 사용자와 권한: 민감 파일을 쓰는 작업은 최소 권한 원칙 적용
- 자원 한도: ulimit와 cgroup(systemd 속성) 확인
- 로깅: 위치·보존 주기·회전 정책 명시
- 재부팅 내구성: ad-hoc이면 nohup, 반복이면 systemd-timer
- 관측성: 진행률 출력, 부분 결과 저장, 실패 시 알림 경로 마련
빠르게 쓰는 스니펫 모음
# 가장 안전한 1줄
nohup <cmd> </dev/null > /var/log/<app>/run.$(date +%F_%H%M%S).log 2>&1 &
# 이미 돌아가는 잡을 세션에서 분리
bg %1 && disown -h %1
# 세션 완전 분리
setsid nohup <cmd> </dev/null > run.log 2>&1 &
# 파이프라인 전체를 보호
nohup bash -c 'producer | filter | consumer' </dev/null > pipe.log 2>&1 &
# systemd로 “작은 서비스”처럼 실행하고 추적
systemd-run --user --unit=adhoc-job bash -lc '<cmd>'
journalctl --user -u adhoc-job -f
마무리
nohup은 SIGHUP 무시와 입출력 분리를 통해 “세션과 운명을 같이하지 않는 작업”을 간단히 만든다. 여기에 < /dev/null과 명시적 로그 리다이렉션을 습관화하면 대부분의 원격 작업은 안정적으로 살아남는다. 파이프라인·세션·재부팅까지 고려해야 하는 시나리오라면 setsid와 systemd-run을 조합해 “작업을 서비스처럼” 다루는 것이 운영 비용과 실패 확률을 함께 줄여준다.