1. 정의 / 결론

nohup은 터미널 종료 시 전달되는 SIGHUP 신호를 무시하도록 만들어, 실행 중인 프로세스가 세션 종료와 함께 같이 죽지 않게 하는 명령어다.
핵심은 단순히 “백그라운드 실행”이 아니라, 프로세스의 생명주기를 현재 터미널 세션으로부터 분리하는 것에 있다.

많은 사람이 nohup을 “서버에서 오래 실행하는 명령 앞에 붙이는 관용구” 정도로 기억하지만, 그 정도로 이해하면 자주 틀린다. nohup은 실행 속도를 높여주는 도구도 아니고, 프로세스를 완전한 서비스처럼 관리해주는 도구도 아니며, 자동 재시작이나 상태 감시를 제공하는 도구도 아니다. nohup이 바꾸는 것은 매우 제한적이다. SIGHUP 처리 방식과 기본 입출력 연결 방식을 바꾼다. 그런데 이 제한적인 변화가 터미널 종료 이후 프로세스의 생존 여부를 결정하기 때문에 실무에서는 매우 큰 차이를 만든다.

리눅스에서 터미널은 단순히 글자를 보여주는 창이 아니다. 프로세스 입장에서는 표준 입력, 표준 출력, 표준 에러가 연결된 실행 환경이며, 동시에 세션과 제어 터미널이라는 실행 문맥을 제공하는 기반이다. 따라서 터미널이 사라진다는 것은 “화면이 닫힌다”는 시각적 사건이 아니라, 해당 세션에 연결된 프로세스에 대해 세션 종료라는 의미 있는 시스템 이벤트가 발생한다는 뜻이다. nohup은 հենց 이 지점에 개입한다.

정리하면 nohup의 본질은 다음 한 문장으로 표현할 수 있다.
“세션이 닫혀도 그 사실 때문에 프로세스가 종료되지 않도록, SIGHUP을 무시하게 만드는 최소 단위 실행 도구.”

2. 핵심 요약

nohup을 이해할 때 가장 먼저 잡아야 할 것은, 이 명령어가 모든 것을 해결하는 만능 생존 장치가 아니라는 점이다. nohup은 프로세스를 터미널 세션에 덜 의존하게 만들 뿐이며, 이 효과가 제대로 완성되려면 보통 &와 함께 사용되어야 한다.

첫째, nohup은 프로세스를 터미널 세션에서 분리하는 방향으로 동작한다. 정확히 말하면 “터미널 세션 종료 시 전달되는 SIGHUP에 의해 죽지 않게 만든다.” 즉, 세션 종료라는 외부 이벤트가 곧바로 프로세스 종료로 이어지는 구조를 끊는다.

둘째, nohup은 실행 방식을 바꾸지 않는다. 이 점이 매우 중요하다. nohup python app.py여전히 foreground 실행이다. 사용자는 여전히 터미널을 점유당하고, 명령이 끝날 때까지 같은 셸에서 다른 작업을 하기가 불편하다. 따라서 실무에서는 거의 항상 nohup ... & 형태가 기본 패턴이 된다.

셋째, 출력은 기본적으로 파일로 리다이렉션된다. 이유는 간단하다. 터미널이 닫히면 더 이상 출력할 대상이 없어지기 때문이다. 그래서 nohup은 stdout과 stderr가 터미널에 직접 의존하지 않도록 기본 처리한다. 이때 명시적으로 출력 파일을 지정하지 않으면 일반적으로 nohup.out 같은 파일이 생성된다.

넷째, 입력은 기본적으로 차단된다. 표준 입력이 더 이상 정상적인 터미널 입력을 기대할 수 없기 때문에, nohup 환경에서는 stdin이 사실상 /dev/null로 연결되는 구조를 갖게 된다. 그래서 사용자 입력을 요구하는 프로그램은 nohup과 맞지 않는다.

결국 실무에서 기억해야 할 최소 구조는 아래다.

nohup command > app.log 2>&1 &

이 한 줄에는 세 가지 의도가 동시에 들어 있다.

  1. SIGHUP 무시
  2. 백그라운드 비동기 실행
  3. 출력 로그를 파일로 명시적 관리

즉, nohup은 혼자서 완결된 도구가 아니라, signal + background execution + redirection이라는 세 가지 요소 중 signal 쪽을 담당하는 도구라고 보는 편이 더 정확하다.

3. 왜 필요한가

리눅스에서 실행되는 대부분의 프로세스는 터미널 세션과 연결되어 있다. 이 연결을 단순히 “명령을 입력한 창” 정도로 이해하면 nohup의 필요성이 잘 보이지 않는다. 실제로는 이 연결이 훨씬 더 본질적이다. 프로세스는 그 터미널을 통해 입력을 받고, 출력을 내보내며, 세션 종료 같은 상태 변화를 시그널 형태로 전달받는다. 다시 말해, 터미널은 단순 표시 장치가 아니라 프로세스 실행 문맥의 일부다.

문제는 이 구조가 서버 환경에서 매우 취약하다는 점이다. 서버에서 긴 작업을 실행할 때는 로컬 콘솔이 아니라 SSH 세션을 통해 접속하는 경우가 많다. 그런데 SSH는 본질적으로 네트워크 위에 떠 있는 세션이다. 네트워크 품질이 나빠질 수도 있고, VPN이 끊길 수도 있고, 터미널 프로그램이 종료될 수도 있고, 사용자가 실수로 창을 닫을 수도 있다. 이 모든 사건은 “화면이 닫혔다”로 끝나는 것이 아니라, 세션이 종료되었다는 시스템 이벤트가 된다.

이때 터미널 세션에 의존하는 프로세스는 보통 그 세션 종료와 함께 종료된다. 이는 이상한 동작이 아니라, 오히려 기본적으로 자연스러운 동작이다. 사용자가 직접 붙어서 실행하던 작업이니, 그 세션이 끝나면 같이 정리되는 것이 일반적인 모델이기 때문이다. 하지만 서버 작업에서는 이 일반적인 모델이 그대로 문제로 바뀐다.

예를 들어 GPU 서버에서 수 시간짜리 학습을 돌린다고 하자. 학습 도중 SSH 세션이 끊기면, 프로세스도 같이 종료될 수 있다. 그러면 지금까지 소비한 GPU 시간은 그대로 날아가고, 중간 체크포인트가 없으면 처음부터 다시 시작해야 한다. 배치 작업에서도 마찬가지다. 수천만 건 데이터를 몇 시간 동안 처리하는 작업이 막바지에 세션 종료로 죽어버리면, 단순히 불편한 수준이 아니라 재처리 비용과 운영 리스크가 바로 발생한다.

기존 구조를 단순화하면 이렇다.

프로세스 실행 → 터미널 세션 종속 → 세션 종료 → SIGHUP 전달 → 프로세스 종료

이 구조에서는 작업 지속성이 사용자의 접속 상태에 종속된다. 서버 작업 입장에서는 매우 나쁜 구조다. 필요한 것은 명확하다. 프로세스의 생명주기가 세션 종료 여부에 의해 직접 결정되지 않게 해야 한다.

nohup은 바로 이 문제를 가장 낮은 비용으로 해결한다. 별도의 데몬을 만들지 않아도 되고, 서비스 유닛 파일을 만들지 않아도 되며, 복잡한 프로세스 매니저를 도입하지 않아도 된다. 단지 실행 시점에 SIGHUP을 무시하게 함으로써, 세션이 끊겨도 프로세스가 그 사건을 “종료 사유”로 받아들이지 않게 만든다.

그 결과 구조는 이렇게 바뀐다.

프로세스 실행 → nohup 적용 → 세션 종료 → SIGHUP 전달 → 무시 → 프로세스 유지

이 변화는 겉보기에는 명령어 하나 차이지만, 실제로는 프로세스 생명주기의 통제권이 터미널에서 프로세스 자신 쪽으로 이동하는 변화다. 사용자의 접속 상태가 곧 작업의 생존 여부를 결정하던 구조가, 작업 자체의 종료 조건이 생존 여부를 결정하는 구조로 바뀌는 것이다.

또 하나 중요한 점은, nohup이 단순히 “죽지 않게만” 하는 것이 아니라 입출력 문제도 함께 처리한다는 것이다. 세션이 닫히면 출력 대상도 사라진다. 그런데 프로세스가 계속 살아 있다면, 출력은 어디론가 가야 한다. 그래서 nohup은 기본적으로 출력 경로를 파일로 유지하려고 하고, 입력은 받을 수 없으므로 차단한다. 즉, nohup은 세션 종료 이후에도 프로세스가 최소한 일관된 IO 상태를 유지하도록 돕는다.

이 때문에 nohup은 단순 편의 기능이 아니라, 서버 작업에서 자주 마주치는 다음 문제를 해결하는 최소 단위 도구가 된다.

  • 접속 종료가 작업 종료로 이어지는 문제
  • 장시간 작업의 지속성이 보장되지 않는 문제
  • 터미널 소실 이후 출력 경로가 사라지는 문제
  • 인터랙티브 입력이 불가능한 상태를 명시적으로 처리해야 하는 문제

결국 nohup이 필요한 이유는 단순하다.
터미널은 사용자 인터페이스이면서 동시에 프로세스 생명주기와 IO를 연결하는 구조인데, 서버 작업에서는 이 연결이 오히려 장애 요인이 되기 때문이다.

4. 예제

예제 1 — nohup 단독 실행의 한계

nohup sleep 60

이 명령은 많은 초보자가 처음 시도하는 형태다. 겉으로 보기에는 이미 nohup을 붙였으니, 뭔가 “독립 실행”이 된 것처럼 느껴진다. 하지만 실제로는 그렇지 않다. 프로세스는 실행되지만, 여전히 foreground 상태로 남아 있다. 즉, 현재 셸은 해당 명령이 끝날 때까지 그 작업에 붙잡혀 있고, 사용자는 그동안 같은 터미널에서 자연스럽게 다음 작업을 진행하기 어렵다.

핵심은 nohup이 바꾸는 것이 실행 흐름이 아니라 신호 처리 방식이라는 점이다. sleep 60 자체는 원래 동기적으로 실행된다. nohup은 이 동기 구조를 비동기로 바꾸지 않는다. 따라서 사용자는 여전히 터미널을 점유당한다.

이 예제가 중요한 이유는, 많은 사람이 “nohup을 붙였으니 이제 백그라운드처럼 동작하겠지”라고 오해하기 때문이다. 그러나 nohup과 백그라운드 실행은 서로 다른 개념이다.

  • nohup: 세션 종료 시 전달되는 SIGHUP을 무시
  • &: 셸 관점에서 명령을 비동기 실행

즉, 이 예제는 nohup의 역할 경계를 명확히 보여준다. nohup은 생존 조건을 바꾸지만, 사용자의 셸 경험을 자동으로 바꾸지는 않는다.

실제로 확인해보면 현재 프롬프트가 바로 돌아오지 않는다. sleep 60이 끝나야 셸 프롬프트가 다시 나타난다. 따라서 실무에서 이 형태는 거의 쓰이지 않는다. 다만 다음과 같은 제한적인 목적으로는 쓸 수 있다.

  • 특정 명령이 nohup 환경에서 어떻게 반응하는지 테스트할 때
  • 출력이 어떻게 처리되는지 빠르게 확인할 때
  • 학습용으로 “nohup은 &가 아니다”를 보여줄 때

예제 2 — 기본적인 실무 패턴

nohup sleep 60 &

이제야 비로소 대부분이 기대하는 형태가 나온다. 이 명령은 sleep 60백그라운드에서 실행하면서, 동시에 세션 종료 시 전달되는 SIGHUP을 무시하게 만든다. 현재 셸은 즉시 프롬프트를 반환하고, 사용자는 같은 터미널에서 다른 작업을 계속할 수 있다. 이후 터미널을 종료해도, 프로세스는 계속 살아 있을 가능성이 높다.

왜 이렇게 되는가를 구조적으로 나누어 보면 다음과 같다.

첫째, &가 셸에게 이 명령을 포그라운드가 아니라 비동기 작업(job) 으로 취급하라고 지시한다. 즉, 셸은 해당 명령이 끝날 때까지 기다리지 않고 바로 다음 프롬프트를 사용자에게 돌려준다.

둘째, nohup은 해당 프로세스가 SIGHUP을 받더라도 종료하지 않도록 한다. 즉, 나중에 사용자가 터미널을 닫거나 SSH 연결이 끊겨도, 적어도 SIGHUP 자체 때문에 죽지는 않게 된다.

보통 실무에서 nohup을 쓴다고 말할 때는 사실상 이 패턴을 의미한다. 예를 들어 다음과 같이 사용할 수 있다.

nohup python train.py &
nohup java -jar app.jar &
nohup bash batch.sh &

다만 이 예제는 동작 원리 설명용 최소 패턴일 뿐이다. 실제 운영에서는 출력 파일을 명시적으로 지정하지 않으면 nohup.out이 생기거나, 로그가 예상치 못한 곳에 쌓여 관리가 불편해질 수 있다. 그래서 실무에서는 이 다음 예제의 형태가 더 중요하다.

예제 3 — 로그 통합 처리

nohup python app.py > app.log 2>&1 &

이 한 줄은 실무에서 가장 자주 보는 nohup 패턴이다. 이유는 단순하다. 프로세스를 오래 살려 놓는 것만으로는 운영이 되지 않기 때문이다. 무슨 일이 일어나는지 관측할 수 있어야 한다. 그 관측의 가장 기본 수단이 로그다.

이 명령을 요소별로 분해하면 다음과 같다.

nohup python app.py   # SIGHUP 무시 상태로 실행
> app.log             # stdout을 app.log 파일로 보냄
2>&1                  # stderr를 stdout이 가는 곳으로 보냄
&                     # 백그라운드 실행

결과적으로 표준 출력과 표준 에러가 모두 app.log에 기록된다. 이 구조의 장점은 명확하다.

첫째, 세션이 종료되어도 출력이 사라지지 않는다.
둘째, 표준 출력과 표준 에러를 한 파일에서 시간 순서대로 볼 수 있다.
셋째, 기본 nohup.out 파일 생성을 피할 수 있다.
넷째, 운영자가 tail -f app.log로 상태를 추적하기 쉽다.

예를 들어 아래처럼 로그를 실시간 확인할 수 있다.

tail -f app.log

또는 에러만 따로 보고 싶다면 다음처럼 검색할 수 있다.

grep -i error app.log

물론 모든 상황에서 stdout과 stderr를 한 파일로 합치는 것이 정답은 아니다. 하지만 단일 프로세스를 빠르게 실행하고 상태를 추적해야 하는 운영 상황에서는 매우 실용적이다. 특히 애플리케이션이 자체적으로 구조화 로그를 stdout/stderr에 잘 쓰고 있다면, 이 패턴은 별도의 서비스 매니저 없이도 상당히 많은 문제를 해결한다.

반대로 로그를 분리해야 하는 경우도 있다. 예를 들면 다음과 같이 쓸 수 있다.

nohup python app.py > app.out 2> app.err &

이 경우 정상 출력과 에러 출력을 분리할 수 있다. 다만 사용자가 제시한 기본 구조를 기준으로 보면, 운영 편의상 통합 로그 한 파일을 쓰는 패턴이 더 자주 등장한다. 핵심은 “nohup이 로그 관리를 대신해준다”가 아니라, nohup 환경에서는 출력 경로를 명시적으로 설계해야 한다는 점이다.

예제 4 — stdin 차단의 영향

nohup python interactive.py &

겉으로 보면 예제 2와 크게 다르지 않아 보인다. 하지만 이 명령은 많은 경우 정상 동작하지 않는다. 그 이유는 interactive.py가 사용자 입력을 요구할 가능성이 크기 때문이다.

nohup 환경에서는 stdin이 일반적인 터미널 입력으로 연결되지 않는다. 보통 /dev/null 수준으로 처리되어, 프로그램이 input()이나 대화형 입력을 기대하더라도 실제로는 아무 입력도 받을 수 없다. 따라서 프로그램은 다음 중 하나의 상태에 빠질 수 있다.

  • 즉시 EOF를 받고 종료
  • 입력 대기 로직에서 비정상 동작
  • 예상치 못한 예외 발생
  • 제대로 된 화면 상호작용이 불가능한 상태

예를 들어 아래 같은 프로그램을 생각해보자.

name = input("Enter your name: ")
print(f"Hello, {name}")

이 프로그램을 nohup으로 실행하면 입력이 정상적으로 들어오지 않는다. 따라서 nohup은 본질적으로 비대화형(non-interactive) 작업에 적합하다. 배치 스크립트, 로그 수집기, 서버 프로세스, 모델 학습, 데이터 변환 작업처럼 “한 번 시작하면 스스로 끝까지 실행되는 유형”에는 잘 맞지만, vim, top, mysql interactive shell, python REPL 같은 프로그램에는 맞지 않는다.

이 예제가 중요한 이유는, 많은 사람이 nohup을 “죽지 않게 해주는 도구”로만 이해하고, 입력 구조 변화까지는 생각하지 않기 때문이다. 그러나 프로세스가 살아 있는 것과 정상적으로 동작 가능한 것은 다르다. 입력이 필요한 프로그램은 살아 있어도 쓸 수 없다.

따라서 실무에서 nohup을 적용하기 전에 먼저 물어야 할 질문은 이것이다.
“이 프로그램은 시작 이후 사용자 입력이 전혀 필요 없는가?”

5. 실무 적용

시나리오 1 — ML 학습 작업

GPU 서버에서 수 시간, 때로는 수십 시간 동안 학습을 수행하는 상황을 생각해보자. 이런 작업은 보통 개발자가 SSH로 서버에 접속한 뒤 직접 실행한다. 문제는 학습 시간이 길수록 세션 종료 가능성이 높아진다는 점이다. 사무실 네트워크가 바뀔 수도 있고, 노트북이 절전 상태에 들어갈 수도 있고, VPN이 끊길 수도 있고, 단순히 터미널 창을 잘못 닫을 수도 있다. 이때 학습 프로세스가 세션에 종속되어 있으면, 모델 학습이 중간에 중단된다.

이 문제는 단순 불편이 아니다. 학습 작업은 CPU나 GPU 자원을 장시간 점유하며, 입력 데이터 로딩, 전처리, 체크포인트 작성, 검증 반복 등을 포함한다. 중간에 프로세스가 죽으면 다음과 같은 비용이 발생한다.

  • 이미 소비한 GPU 시간이 그대로 손실됨
  • 로그가 끊겨 원인 분석이 어려워짐
  • 체크포인트가 없다면 처음부터 재실행해야 함
  • 같은 실험 조건 재현이 어려워질 수 있음

이런 상황에서 가장 흔한 운영 패턴은 다음과 같다.

nohup python train.py > train.log 2>&1 &

또는 실험 파라미터별로 로그를 구분하고 싶다면 다음처럼 쓸 수 있다.

nohup python train.py --model xgb --fold 3 > logs/train_xgb_fold3.log 2>&1 &

이 구조의 장점은 명확하다.
학습은 세션 종료와 무관하게 계속 진행되고, 모든 출력은 로그 파일에 남아 나중에 분석 가능하다. 사용자는 SSH를 다시 접속한 뒤 아래처럼 로그를 볼 수 있다.

tail -f logs/train_xgb_fold3.log

또는 프로세스가 살아 있는지 확인할 수 있다.

ps -ef | grep train.py

실무에서는 여기에 추가로 PID 파일 저장이나 날짜 기반 로그 파일명 관리까지 붙일 수 있다. 예를 들면:

nohup python train.py > logs/train_$(date +%F_%H%M%S).log 2>&1 &
echo $! > train.pid

여기서 $!는 방금 백그라운드로 띄운 마지막 프로세스의 PID다. 이렇게 하면 추후 종료 관리도 쉬워진다.

kill $(cat train.pid)

즉, ML 학습 작업에서 nohup은 단순한 명령 습관이 아니라, 장시간 비대화형 작업을 접속 상태와 분리하고, 로그를 남기며, 재접속 이후에도 추적 가능하게 만드는 최소 운영 패턴이다.

시나리오 2 — Java 서비스 실행

jar 기반 애플리케이션을 서버에서 임시 또는 준운영 형태로 실행하는 상황도 매우 흔하다. 예를 들어 빠르게 동작을 확인해야 하는 내부 도구, 배치성 API 서버, 검증용 프로세스, 혹은 아직 systemd 등록 전 단계의 애플리케이션 등이 그렇다.

이 경우 가장 단순한 실행은 보통 다음과 같다.

java -jar app.jar

하지만 이렇게 실행하면 현재 셸이 종료될 때 서비스도 같이 중단될 수 있다. 운영 환경에서는 이 구조가 취약하다. 서비스가 비록 정식 데몬은 아니더라도, 최소한 사용자의 터미널 세션과는 분리되어야 한다.

그래서 흔히 아래와 같은 형태를 쓴다.

nohup java -jar app.jar > app.log 2>&1 &

이 명령은 서비스의 생존을 세션 종료로부터 분리하고, 동시에 초기 로그까지 파일에 남긴다. 실무에서는 다음과 같이 JVM 옵션과 함께 쓰기도 한다.

nohup java -Xms512m -Xmx2048m -jar app.jar \
  --spring.profiles.active=prod \
  > logs/app.log 2>&1 &

이후 상태 확인은 보통 아래처럼 한다.

ps -ef | grep app.jar
tail -f logs/app.log

다만 여기서 한 가지는 분명히 해야 한다. nohup은 서비스 실행 도구이지 서비스 관리 도구가 아니다. 즉, 아래 기능은 기본 제공하지 않는다.

  • 프로세스 자동 재시작
  • 부팅 시 자동 시작
  • health check 기반 재기동
  • 표준화된 PID 관리
  • 자원 제한 및 보안 정책 적용
  • 실행 사용자, 작업 디렉터리, 의존성 순서 관리

따라서 Java 서비스를 정말 운영 서비스로 굴려야 한다면 systemd나 supervisor 같은 도구가 더 적절하다. nohup은 그 전 단계, 혹은 간단한 실행에는 충분하지만, 운영 체계 전체를 대신하지는 못한다.
중요한 것은 “nohup을 써서 서비스가 계속 돈다”가 아니라, 서비스는 사용자 세션이 아니라 시스템 수준에서 관리되어야 한다는 구조를 이해하는 것이다.

시나리오 3 — 대량 데이터 배치 처리

수십 분 또는 수 시간 이상 걸리는 배치 작업은 nohup의 전형적인 사용 대상이다. 예를 들어 다음 같은 작업을 떠올리면 된다.

  • 대규모 CSV 정제
  • 데이터 마이그레이션
  • 대량 API 호출 후 저장
  • 로그 집계 및 통계 생성
  • 머신러닝 피처 생성
  • DB 비교/검증 작업

이런 배치 작업은 대체로 비대화형이고, 한 번 시작하면 끝까지 자동으로 수행된다. 그런데 문제는 처리 시간이 길수록 세션 종료 가능성이 높아지고, 중간 종료 시 비용이 급격히 커진다는 점이다. 특히 데이터 처리 작업은 중간 상태가 누적되기 때문에, 프로세스가 갑자기 죽으면 결과 정합성까지 확인해야 한다.

예를 들어:

nohup python batch_etl.py > batch_etl.log 2>&1 &

또는 쉘 스크립트 기반이면:

nohup bash nightly_batch.sh > nightly_batch.log 2>&1 &

이렇게 실행하면 작업은 지속되고, 로그에는 어느 단계까지 진행되었는지 남는다. 만약 배치가 여러 단계로 이뤄져 있다면 로그가 매우 중요하다. 아래 같은 메시지가 남을 수 있기 때문이다.

[STEP 1/5] read source files
[STEP 2/5] normalize schema
[STEP 3/5] enrich records
[STEP 4/5] write output
[STEP 5/5] verify row counts

이런 로그가 있어야 실패 지점을 빠르게 파악할 수 있다. 반대로 nohup 없이 실행했다가 세션 종료로 죽으면, 작업이 어디까지 갔는지도 불명확해질 수 있다.

대량 배치에서 nohup의 실질적 효과는 세 가지다.

  1. 작업 완료 가능성을 높인다
  2. 로그를 남겨 중단 지점을 확인 가능하게 한다
  3. 사용자 접속 상태와 작업 지속성을 분리한다

즉, nohup은 단순히 “명령을 오래 돌리는 법”이 아니라, 실패 비용이 큰 비대화형 장시간 작업을 최소한의 운영 형태로 안정화하는 도구다.

시나리오 4 — 로그 수집 프로세스

지속적으로 로그를 수집하거나, 특정 디렉터리를 감시하거나, 외부 소스에서 이벤트를 끌어오는 프로세스도 nohup과 잘 맞는다. 이런 작업은 사용자 입력이 거의 필요 없고, 종료 조건이 명확하지 않거나 장시간 실행되는 경우가 많다.

예를 들어 다음 같은 스크립트를 생각해볼 수 있다.

nohup python collect_logs.py > collect_logs.log 2>&1 &

또는 파일 감시형이라면:

nohup bash watch_ingest.sh > watch_ingest.log 2>&1 &

이 상황에서 핵심은 “세션 종료 후에도 수집이 계속되어야 한다”는 점이다. 로그 수집기나 워처 프로세스가 사용자의 SSH 연결 여부에 따라 멈추면, 수집 공백이 생기고 운영 데이터가 끊긴다. 특히 로그는 연속성이 중요하기 때문에, 한 번 수집이 멈췄다가 다시 시작되면 그 사이 구간을 복구하기 어려울 수 있다.

또한 이런 프로세스는 보통 stdout/stderr 자체가 진단 정보이기도 하다. 따라서 다음처럼 로그 파일을 따로 지정하는 것이 중요하다.

nohup python collect_logs.py > logs/collector_$(date +%F).log 2>&1 &

이후 운영자는 다음처럼 추적할 수 있다.

tail -f logs/collector_2026-03-27.log

이 구조는 정식 서비스 관리 체계만큼 견고하지는 않지만, 빠르게 지속 실행형 프로세스를 띄워야 할 때는 매우 실용적이다. 핵심은 nohup이 “로그 수집” 기능을 제공하는 것이 아니라, 로그 수집 프로세스의 연속성을 세션 종료로부터 보호해 준다는 점이다.

6. 흔한 실수

실수 1 — nohup만 사용

잘못된 사용:

nohup python app.py

이 형태는 얼핏 보면 문제가 없어 보인다. 이미 nohup을 붙였으니 프로세스가 독립적으로 살아남을 것 같기 때문이다. 하지만 실제로는 foreground 실행이다. 즉, 현재 셸은 여전히 해당 프로세스가 끝날 때까지 기다린다. 사용자는 같은 터미널에서 다른 명령을 자연스럽게 수행하기 어렵고, 실행 경험도 기대와 다르다.

여기서 더 정확히 말하면 “터미널 종료 시 영향 받는다”는 표현은 부정확하게 이해될 수 있다. SIGHUP 자체는 무시하므로 세션 종료 시 프로세스가 살아남을 가능성은 있다. 하지만 현재 셸을 계속 점유하고 있다는 점이 큰 문제다. 사용자는 보통 nohup을 붙이면 즉시 프롬프트가 돌아오길 기대하는데, 그렇지 않다.

원인은 명확하다. nohup은 실행 방식을 바꾸지 않는다.
해결은 &를 붙여 비동기 실행으로 만드는 것이다.

nohup python app.py &

실무적으로는 여기서 한 단계 더 나아가 로그 파일까지 지정해야 한다.

nohup python app.py > app.log 2>&1 &

즉, nohup만 붙이는 실수는 본질적으로 signal 처리와 실행 제어를 혼동한 결과다.

실수 2 — 로그 방치

잘못된 사용:

nohup python app.py &

이 명령은 일단 돌아간다. 그래서 초기에 문제를 체감하지 못하는 경우가 많다. 그러나 출력 경로를 명시하지 않았기 때문에, 기본적으로 nohup.out 파일이 생성되거나 예상치 못한 위치에 로그가 쌓일 수 있다. 이 파일은 시간이 지나면 계속 커지고, 여러 번 실행하면 어느 실행의 로그인지도 구분하기 어려워진다.

운영 관점에서 이는 꽤 나쁜 습관이다. 이유는 다음과 같다.

  • 로그 위치가 명확하지 않다
  • 파일이 무제한으로 커질 수 있다
  • 여러 실행이 같은 파일에 섞일 수 있다
  • 디버깅 시점에 로그를 찾느라 시간을 낭비한다

해결은 단순하다. 항상 출력 경로를 명시적으로 설계하면 된다.

nohup python app.py > app.log 2>&1 &

또는 날짜를 포함한 로그 파일로 분리할 수도 있다.

nohup python app.py > logs/app_$(date +%F_%H%M%S).log 2>&1 &

실무에서는 nohup보다 로그 설계가 더 중요할 때도 많다. 살아 있는 프로세스보다, 무슨 일이 일어나는지 알 수 없는 프로세스가 더 위험하기 때문이다.

실수 3 — 인터랙티브 프로그램 실행

잘못된 사용:

nohup vim &

이 경우 많은 사용자가 “vim이 백그라운드에서 살아 있겠지”라고 생각할 수 있다. 하지만 실제로는 정상적인 사용이 불가능하다. vim은 본질적으로 대화형 프로그램이며, 터미널 입력과 화면 제어를 전제로 동작한다. nohup 환경에서는 stdin이 차단되고, 터미널과의 상호작용도 기대할 수 없기 때문에 정상 동작하지 않는다.

이 문제는 vim에만 국한되지 않는다. 다음과 같은 프로그램도 원칙적으로 같은 범주다.

  • top
  • less
  • mysql 인터랙티브 셸
  • python REPL
  • 사용자 입력을 요구하는 임의의 스크립트

원인은 nohup이 stdin을 사실상 /dev/null 수준으로 처리하기 때문이다.
해결은 간단하다. 인터랙티브 프로그램에는 nohup을 쓰지 않는다.

만약 세션 종료 이후에도 대화형 세션을 유지해야 한다면 nohup이 아니라 screen이나 tmux 같은 도구를 써야 한다. nohup은 “대화형 상태 보존 도구”가 아니라, 비대화형 실행 지속 도구다.

실수 4 — PID 관리 없음

잘못된 사용은 명령 자체보다 운영 습관에 있다. 실행은 해놓고, 나중에 어떤 PID가 해당 프로세스인지 추적하지 않는 경우다. 처음에는 별 문제가 없어 보인다. 하지만 시간이 지나면 같은 앱이 여러 개 떠 있는지, 이전 실행이 아직 살아 있는지, 지금 종료해야 할 PID가 무엇인지 파악하기 어려워진다.

예를 들어 이런 상황이 생긴다.

  • 같은 명령을 여러 번 실행해 중복 프로세스가 발생
  • 어떤 로그 파일이 어느 프로세스의 것인지 불분명
  • 종료할 때 grep 결과에서 잘못된 PID를 잡을 위험
  • 좀비처럼 남은 프로세스를 뒤늦게 발견

많은 사람이 다음처럼 대충 확인한다.

ps -ef | grep app
kill -9 PID

이 방법 자체가 틀린 것은 아니다. 하지만 운영적으로는 거칠고 실수 가능성이 높다. 특히 kill -9는 정상 종료 기회를 주지 않기 때문에, 가능한 기본 종료 신호부터 쓰는 편이 낫다.

더 나은 패턴은 실행 직후 PID를 저장하는 것이다.

nohup python app.py > app.log 2>&1 &
echo $! > app.pid

이후에는 다음처럼 관리할 수 있다.

cat app.pid
ps -fp $(cat app.pid)
kill $(cat app.pid)

정상 종료가 안 될 때만 마지막 수단으로 강제 종료를 고려한다.

kill -9 $(cat app.pid)

즉, nohup 자체가 문제가 아니라 프로세스를 띄운 뒤 관리 전략이 없는 것이 문제다. nohup은 실행을 쉽게 만들어 주지만, 그만큼 운영자가 관리 책임을 직접 가져야 한다.

실수 5 — 로그 파일 공유

잘못된 사용은 다음처럼 나타난다.

nohup python worker_a.py > app.log 2>&1 &
nohup python worker_b.py > app.log 2>&1 &
nohup python worker_c.py > app.log 2>&1 &

겉보기에는 편하다. 파일 하나만 보면 되니 관리가 쉬워 보인다. 하지만 실제 운영에서는 금방 문제가 된다. 여러 프로세스가 하나의 로그 파일을 공유하면 출력이 뒤섞이고, 어느 메시지가 어느 프로세스에서 나온 것인지 구분하기 어렵다. 특히 에러가 동시에 발생하면 디버깅 난이도가 급격히 올라간다.

문제의 본질은 단순히 로그가 섞인다는 데 있지 않다. 관측 가능성이 무너진다.
운영 로그는 읽기 쉬워야 하는 것이 아니라, 원인 추적이 가능해야 한다. 그런데 서로 다른 프로세스의 stdout/stderr가 한 파일에 무질서하게 섞이면, 다음이 어려워진다.

  • 에러 발생 주체 식별
  • 재시작 시점 추적
  • 프로세스별 성능 분석
  • 장애 영향 범위 분리

해결은 명확하다. 프로세스별로 로그를 분리한다.

nohup python worker_a.py > logs/worker_a.log 2>&1 &
nohup python worker_b.py > logs/worker_b.log 2>&1 &
nohup python worker_c.py > logs/worker_c.log 2>&1 &

또는 날짜와 PID를 포함해 더 명확히 구분할 수도 있다.

nohup python worker_a.py > logs/worker_a_$(date +%F_%H%M%S).log 2>&1 &

즉, nohup 환경에서는 출력이 파일로 가기 쉬운 만큼, 로그 분리 설계가 곧 운영 설계가 된다.

실수 6 — nohup을 만능으로 오해

이 실수는 가장 구조적이다. nohup으로 프로세스가 죽지 않게 만들 수 있다는 사실 때문에, 이를 서비스 관리 도구처럼 오해하는 경우가 많다. 예를 들어 “서비스는 nohup으로 띄우면 되지 않나?” 같은 식이다. 이 생각은 짧은 실험 단계에서는 맞을 수 있지만, 운영 단계에서는 빠르게 한계를 드러낸다.

nohup은 다음을 제공하지 않는다.

  • 비정상 종료 시 자동 재시작
  • 표준화된 시작/중지/재시작 명령
  • 부팅 시 자동 기동
  • 서비스 상태 질의
  • 자원 제한
  • 보안 컨텍스트 분리
  • 로그 회전 정책
  • 의존성 있는 서비스 순서 제어

즉, nohup은 프로세스를 “살려 놓는 것”까지만 돕는다. 그 이후의 운영 체계는 사용자가 직접 만들어야 한다. 그래서 장기 운영 서비스에 nohup만 쓰면, 시간이 지나면서 PID 관리, 중복 실행, 로그 누적, 비정상 종료 방치, 재부팅 후 미기동 같은 문제가 쌓인다.

해결은 역할을 분리해서 보는 것이다.

  • 일회성/임시 장시간 작업 → nohup 적합
  • 정식 운영 서비스 → systemd, supervisor, 컨테이너 오케스트레이션 등 사용

이 차이를 이해하지 못하면 nohup은 편한 도구가 아니라, 나중에 운영 부채를 만드는 출발점이 된다.

7. 관련 개념

SIGHUP은 세션 종료 시 전달되는 대표적인 시그널이다. nohup의 핵심은 바로 이 신호를 무시하도록 만드는 데 있으므로, SIGHUP을 이해하지 않고 nohup을 이해하는 것은 불가능하다. SIGHUP은 단순한 강제 종료 명령이 아니라, “연결되어 있던 터미널 세션이 끝났다”는 사건을 프로세스에게 알려주는 의미를 가진다.

&는 백그라운드 실행 문법이다. nohup과 자주 함께 쓰이지만, 역할은 완전히 다르다. &는 셸에게 해당 명령을 비동기로 실행하라고 지시할 뿐이고, 세션 종료 시 생존 여부는 직접 해결하지 않는다. 따라서 command &만으로는 충분하지 않고, 세션 종료까지 고려하면 nohup command &가 필요해진다.

stdout과 stderr는 출력 스트림 구조를 이해하는 핵심이다. nohup 환경에서는 터미널이 사라진 뒤에도 출력 경로가 유지되어야 하므로, 이 두 스트림을 어디로 보낼지 명시적으로 설계해야 한다. 그래서 > file 2>&1 같은 리다이렉션 문법이 사실상 nohup 사용법의 일부처럼 따라붙는다.

/dev/null은 입력 차단 장치로 이해하면 좋다. nohup은 stdin이 더 이상 터미널 입력을 기대할 수 없는 상황을 다루기 위해, 입력 경로를 사실상 비워 버리는 구조를 갖는다. 이 점 때문에 인터랙티브 프로그램과 nohup은 잘 맞지 않는다.

실무에서 함께 알아두면 좋은 추가 개념도 있다.

  • ps, pgrep → 프로세스 확인
  • kill, kill -15, kill -9 → 프로세스 종료
  • tail -f → nohup 로그 추적
  • screen, tmux → 대화형 세션 유지
  • systemd, supervisor → 정식 서비스 관리

즉, nohup은 혼자 존재하는 개념이 아니라 signal, job control, redirection, process management의 교차점에 있는 도구다.

8. 더 깊이 보기

nohup의 본질은 단순한 편의 명령이 아니다. 더 정확히 말하면, 리눅스 시그널 처리 모델의 일부를 실행 시점에 바꾸는 도구다. 이 점을 이해하면 왜 nohup이 그렇게 동작하는지, 그리고 왜 그 한계가 분명한지도 같이 보인다.

리눅스에서 프로세스는 외부와 완전히 분리된 고립체가 아니다. 운영체제는 시그널을 통해 프로세스에게 상태 변화나 제어 요청을 전달할 수 있다. 예를 들어 SIGINT는 사용자의 인터럽트를 의미하고, SIGTERM은 정상 종료 요청이며, SIGHUP은 전통적으로 터미널 또는 세션 연결 종료와 관련된 의미를 가진다. 프로세스는 이런 시그널을 받았을 때 기본 동작을 따르거나, 자체 핸들러를 등록해 다른 식으로 반응할 수 있다.

이때 대부분의 프로그램은 SIGHUP을 받으면 종료하는 쪽이 자연스럽다. 사용자가 붙어 있던 세션이 끝났으니, 함께 정리되는 것이 기본 모델이기 때문이다. 문제는 서버 환경에서 이 “정상적인 기본값”이 오히려 작업 지속성을 깨뜨린다는 데 있다. nohup은 სწორედ 이 기본값을 바꾼다. 즉, 실행할 명령이 SIGHUP을 받아도 그 신호를 종료 사유로 받아들이지 않도록 설정한다.

여기서 중요한 것은 nohup이 모든 시그널을 무시하게 만드는 것이 아니라는 점이다. nohup의 관심사는 SIGHUP이다. 즉, 세션 종료에 의한 종료를 막는 것이 목적이지, 운영체제 수준의 모든 종료 요청이나 장애 상황을 무효화하는 것이 목적이 아니다. 그래서 nohup으로 띄운 프로세스도 kill 명령으로 종료할 수 있고, 애플리케이션 내부 예외로 죽을 수도 있고, 시스템 재부팅 시 사라질 수도 있다. 이 점 때문에 nohup을 “영원히 죽지 않는 장치”로 이해하면 틀린다.

또한 nohup은 IO 구조까지 함께 건드린다. 프로세스가 살아남아도, 세션이 사라지면 원래 연결되어 있던 터미널 입출력은 더 이상 유효하지 않다. 프로세스가 계속 stdout에 무언가를 쓰려고 하는데 그 대상이 사라졌다면, 동작은 불안정해질 수 있다. 그래서 nohup은 출력 경로를 파일로 유지하려고 하고, 입력은 받을 수 없으므로 차단한다.

즉, nohup은 실제로 다음 세 가지를 동시에 처리한다.

  • 시그널 처리 변경
    세션 종료 시 전달되는 SIGHUP을 무시한다.
  • 출력 경로 유지
    stdout/stderr가 터미널 소실 이후에도 어딘가로 기록되도록 기본 처리한다.
  • 입력 차단
    더 이상 존재하지 않는 대화형 입력을 기대하지 않도록 stdin을 차단한다.

이 세 가지 중 하나라도 빠지면, 세션 종료 이후 프로세스가 안정적으로 살아남는다는 보장이 약해진다. 예를 들어 SIGHUP만 무시해도, 출력 경로가 꼬이거나 입력을 기대하는 프로그램이라면 정상 동작은 어려울 수 있다. 따라서 nohup은 단순히 “시그널 하나를 무시한다”가 아니라, 터미널 소실 이후에도 최소한 일관된 실행 환경을 유지하도록 만드는 작은 실행 래퍼로 보는 편이 정확하다.

한편 더 깊이 들어가면, nohup과 완전히 다른 접근도 있다는 점을 알아둘 필요가 있다. 예를 들어 tmuxscreen은 프로세스를 세션에서 끊어내는 대신, 세션 자체를 유지 가능한 가상 터미널로 만든다. 그래서 대화형 프로그램도 계속 사용할 수 있다. 반면 nohup은 세션을 유지하지 않는다. 입력이 필요 없는 비대화형 작업만 살리는 방식이다. 이 차이는 도구 선택 기준이 된다.

또한 systemd 같은 서비스 매니저는 nohup보다 훨씬 넓은 범위를 다룬다. 단지 SIGHUP을 무시하게 만드는 것이 아니라, 프로세스의 생명주기를 아예 시스템 정책 차원에서 관리한다. 실행 사용자, 재시작 정책, 의존성, 로그 수집, 상태 질의, 부팅 시 자동 시작 등을 함께 제공한다. 따라서 nohup은 서비스 관리의 대체재가 아니라, 그보다 훨씬 좁은 범위의 실행 보조 도구다.

결국 nohup의 깊은 의미는 이것이다.
리눅스에서 프로세스는 원래 터미널과 여러 방식으로 얽혀 있고, nohup은 그 얽힘 중 “세션 종료로 인한 종료”라는 한 축만 잘라내는 도구다. 작지만 정확한 역할을 수행하고, 그래서 자주 쓰이지만, 역할 범위를 넘어서 이해하면 바로 오해가 된다.

9. 정리

nohup은 프로세스를 터미널 세션에서 분리하는 최소 단위 도구다. 정확히는, 터미널 종료 시 전달되는 SIGHUP을 무시하도록 만들어, 세션 종료가 곧 프로세스 종료로 이어지지 않게 한다.

핵심은 nohup이 모든 것을 해결하지 않는다는 점이다. nohup은 실행 방식을 바꾸지 않으므로, 실무에서는 보통 &와 결합해야 현재 셸을 점유하지 않는 완전한 비동기 실행 형태가 된다. 즉, nohup은 생존 조건을 바꾸고, &는 실행 방식을 바꾼다. 이 둘은 역할이 다르며 같이 써야 기대하는 결과가 나온다.

또한 로그 리다이렉션은 사실상 필수다. 세션이 사라져도 출력이 남아야 하고, 나중에 상태를 추적할 수 있어야 하기 때문이다. 그래서 다음 형태가 가장 기본적인 실무 패턴이 된다.

nohup command > app.log 2>&1 &

반대로 입력이 필요한 프로그램에는 nohup을 사용할 수 없다. stdin이 차단되므로, 인터랙티브 프로그램은 살아 있어도 정상적으로 동작하지 않는다. 이런 경우는 nohup이 아니라 tmux, screen, 혹은 다른 세션 유지 도구가 더 적절하다.

마지막으로, nohup은 서비스 관리 도구가 아니다. 자동 재시작, 상태 관리, 부팅 시 자동 기동 같은 운영 기능은 제공하지 않는다. 따라서 장기 운영 서비스에는 systemd나 supervisor 같은 별도 도구가 필요하다.

정리하면 nohup은 다음 범위에서 가장 적절하다.

  • 장시간 실행되는 비대화형 작업
  • SSH 종료와 무관하게 계속 살아야 하는 프로세스
  • 간단한 배치, 학습, 로그 수집, 임시 서버 실행
  • 최소한의 비용으로 세션 종속성을 제거해야 하는 상황

즉, nohup은 작지만 매우 정확한 도구다.
프로세스를 영구 불멸하게 만드는 도구가 아니라, 세션 종료 때문에 죽지 않게 만드는 도구.
이 선을 정확히 이해하면 nohup은 매우 유용하고, 그 선을 넘어서 기대하면 바로 한계가 드러난다.