
서비스 · 프로세스 관리 10 / 50 · Part 1. 프로세스 기초
실습 환경: Rocky Linux 9.8 · Ubuntu 24.04.5 (systemd로 부팅한 Docker 격리 컨테이너, 테스트 계정analyst)
SSH로 접속해 오래 걸리는 작업을 &로 백그라운드에 걸어 두고 로그아웃했는데, 다음 날 보니 작업이 중간에 죽어 있었다. 반대로 공격자가 남겨 둔 프로세스는 로그아웃 후에도 멀쩡히 살아 있다. 이 차이는 세션, 프로세스 그룹, SIGHUP 을 이해하면 설명된다.
이번 글에서는 jobs, bg, fg로 작업 상태를 바꿔 보고, nohup, setsid, disown 중 실제로 로그아웃에서 살아남는 방법 을 직접 로그아웃해서 확인한다.
| 명령 | 동작 |
|---|---|
cmd & | 백그라운드로 실행 |
jobs -l | 현재 셸의 작업 목록(작업 번호, PID, 상태) |
Ctrl+Z | 포그라운드 작업에 SIGTSTP → 정지 |
bg %N | 정지된 작업을 백그라운드에서 계속 실행 (SIGCONT) |
fg %N | 작업을 포그라운드로 가져옴 |
kill %N | 작업 번호로 시그널 전송 |
%+ / %- | 현재 작업 / 이전 작업 |
| 용어 | 의미 | 확인 |
|---|---|---|
| 프로세스 그룹 (PGID) | 파이프라인처럼 함께 제어되는 프로세스 묶음 = 셸의 "작업" | ps -o pgid |
| 세션 (SID) | 로그인 한 번에 생기는 프로세스 그룹들의 묶음 | ps -o sid |
| 제어 터미널 | 세션이 연결된 터미널 (pts/0) | ps -o tty |
| TPGID | 터미널의 현재 포그라운드 프로세스 그룹 | ps -o tpgid |
터미널 연결이 끊기면(hang up) 커널은 그 터미널의 포그라운드 프로세스 그룹과 세션 리더 에게 SIGHUP을 보낸다. 대화형 bash는 SIGHUP을 받으면 자신의 작업들에도 SIGHUP을 전달한다. SIGHUP의 기본 동작은 종료 다.
| 방법 | 원리 |
|---|---|
nohup | SIGHUP을 무시(SIG_IGN) 로 설정한 뒤 exec. 출력이 터미널이면 nohup.out으로 돌린다 |
setsid | 새 세션 을 만들어 기존 터미널과 관계를 끊는다 |
disown | bash의 작업 목록에서만 제거 → bash가 SIGHUP을 전달하지 않을 뿐, 커널이 보내는 SIGHUP은 막지 못한다 |

SSH 세션 (SID 452, 제어 터미널 pts/0)
├─ PGID 452 : bash, sleep 301, sleep 302(nohup), sleep 303(disown) ← 포그라운드 그룹
└─ SID 457 : sleep 304 (setsid, 터미널 없음) ← 별도 세션
로그아웃 → pts/0 hang up → 커널이 포그라운드 그룹(452)에 SIGHUP
sleep 301 : 기본 동작(종료)
sleep 302 : SIGHUP 무시 → 생존 → 부모 bash 종료 → PPID 1 (08편 고아)
sleep 303 : disown 했지만 같은 그룹 → 종료
sleep 304 : 다른 세션 → SIGHUP 대상 아님 → 생존
이 실습은 비대화형 스크립트 로 실행되어 백그라운드 작업이 셸과 같은 프로세스 그룹(452)에 들어갔다. 대화형 셸(job control 활성)에서는 작업마다 별도 그룹이 생기고, bash가 종료 시 작업에 SIGHUP을 보낼지(shopt huponexit)에 따라 결과가 달라질 수 있다. 그래서 확실한 방법은 nohup 또는 setsid 다.
# 1) (Rocky) job control
set -m # 스크립트에서 job control 활성화 (대화형 셸은 기본 활성)
sleep 100 &
sleep 200 &
jobs -l
kill -TSTP %2; jobs -l # Ctrl+Z 와 같은 효과
bg %2; jobs -l
sleep 3 & fg %3; echo "fg 종료 코드=$?"
ps -o pid,pgid,sid,tpgid,stat,cmd -t $(tty | sed 's#/dev/##')
# 2) (Ubuntu) 네 가지 방식으로 실행 후 로그아웃
sleep 301 &
nohup sleep 302 &
sleep 303 & disown
setsid sleep 304 < /dev/null > /dev/null 2>&1 &
ps -o pid,ppid,pgid,sid,stat,cmd -C sleep
grep SigIgn /proc/$(pgrep -f 'sleep 302')/status
exit
# 3) 다시 로그인해서 확인
ps -o pid,ppid,sid,stat,tty,cmd -C sleep
ls -l ~/nohup.out



텍스트 원본(실제 출력):
[analyst@rocky9-lab ~]$ set -m
[analyst@rocky9-lab ~]$ sleep 100 &
[analyst@rocky9-lab ~]$ sleep 200 &
[analyst@rocky9-lab ~]$ jobs -l
[1]- 640 Running sleep 100 &
[2]+ 641 Running sleep 200 &
[analyst@rocky9-lab ~]$ kill -TSTP %2; sleep 0.2; jobs -l
[1]- 640 Running sleep 100 &
[2]+ 641 Stopped sleep 200
[analyst@rocky9-lab ~]$ bg %2; jobs -l
[2]+ sleep 200 &
[1]- 640 Running sleep 100 &
[2]+ 641 Running sleep 200 &
[analyst@rocky9-lab ~]$ sleep 3 & fg %3; echo "fg 종료 코드=$?"
sleep 3
fg 종료 코드=0
[analyst@rocky9-lab ~]$ ps -o pid,pgid,sid,tpgid,stat,cmd -t $(tty | sed 's#/dev/##')
PID PGID SID TPGID STAT CMD
616 616 616 647 Ss bash /tmp/run_10_1.sh
640 640 616 647 S sleep 100
641 641 616 647 S sleep 200
647 647 616 647 R+ ps -o pid,pgid,sid,tpgid,stat,cmd -t pts/0
[analyst@rocky9-lab ~]$ kill %1 %2
analyst@ubuntu-lab:~$ cd ~; rm -f nohup.out
analyst@ubuntu-lab:~$ sleep 301 &
analyst@ubuntu-lab:~$ nohup sleep 302 &
analyst@ubuntu-lab:~$ sleep 303 & disown
analyst@ubuntu-lab:~$ setsid sleep 304 < /dev/null > /dev/null 2>&1 &
analyst@ubuntu-lab:~$ sleep 0.3; ps -o pid,ppid,pgid,sid,stat,cmd -C sleep
nohup: appending output to 'nohup.out'
PID PPID PGID SID STAT CMD
454 452 452 452 S+ sleep 301
455 452 452 452 S+ sleep 302
456 452 452 452 S+ sleep 303
457 452 457 457 Ss sleep 304
analyst@ubuntu-lab:~$ grep SigIgn /proc/$(pgrep -f 'sleep 302')/status
SigIgn: 0000000000000007
analyst@ubuntu-lab:~$ ps -o pid,ppid,sid,stat,tty,cmd -C sleep
PID PPID SID STAT TT CMD
455 1 452 S ? sleep 302
457 1 457 Ss ? sleep 304
analyst@ubuntu-lab:~$ ls -l ~/nohup.out 2>&1
-rw------- 1 analyst analyst 0 Sep 24 11:50 /home/analyst/nohup.out
analyst@ubuntu-lab:~$ pkill -u analyst -x sleep; true
| 관찰 | 의미 |
|---|---|
[1]- 640 Running, [2]+ 641 Running | 작업 번호, PID, 상태. +는 현재 작업(fg/bg 기본 대상) |
TSTP 후 [2]+ Stopped | Ctrl+Z와 같은 SIGTSTP로 정지 |
bg %2 → Running | SIGCONT를 받아 백그라운드에서 계속 실행 |
fg %3 → sleep 3 출력 후 종료 코드 0 | 작업을 포그라운드로 가져와 끝날 때까지 기다렸다 |
| PGID 640, 641, 647이 각각 다름 | job control이 켜지면 작업마다 별도 프로세스 그룹 이 생긴다 |
모두 SID 616 | 같은 로그인 세션. 세션 리더는 bash(616, STAT Ss) |
TPGID 647 = ps 자신의 PGID, STAT R+ | 지금 터미널의 포그라운드 그룹은 실행 중인 ps다 |
Ubuntu: 301·302·303 모두 PGID 452, STAT S+ | 비대화형 실행이라 셸과 같은 포그라운드 그룹 에 들어갔다 |
304는 PGID 457 SID 457 Ss | setsid로 새 세션의 리더가 되었다 |
nohup: appending output to 'nohup.out' | 출력이 터미널이라 파일로 돌렸다 |
SigIgn: 0000000000000007 | 비트 1·2·3 = SIGHUP·SIGINT·SIGQUIT 무시. HUP 무시는 nohup이, INT·QUIT 무시는 비대화형 셸의 백그라운드 작업 규칙이 설정했다 |
| 재로그인 후 302·304만 남음 | nohup과 setsid만 생존. disown(303)은 커널이 보낸 SIGHUP에 종료되었다 |
생존한 두 프로세스의 PPID 1, TTY ? | 부모 셸이 사라져 고아가 되었고, 터미널도 없다 |
| 302의 SID는 여전히 452 | 세션은 끝났지만 옛 세션 번호가 흔적으로 남는다. 출처 추적 단서가 된다 |
| 주제 | 내용 |
|---|---|
| 지속 실행 | nohup ./x &, setsid ./x는 공격자가 셸 접속이 끊겨도 도구를 계속 돌리는 가장 흔한 방법이다 |
| 흔적 | nohup은 기본적으로 nohup.out을 남긴다. 공격자의 홈·웹 루트에서 발견되는 nohup.out은 실행 흔적이다 |
| 세션 번호 | 로그아웃 후 남은 프로세스의 SID는 원래 로그인 세션 을 가리킨다. loginctl, 인증 로그와 연결해 누가 남겼는지 찾는다 |
| 정책 | systemd-logind의 KillUserProcesses=yes로 설정하면 로그아웃 시 세션 cgroup의 프로세스를 모두 정리한다. 배포판 기본값은 no다 |
| 올바른 운영 | 운영 작업을 nohup으로 띄우면 재부팅·장애 시 관리가 안 된다. systemd 서비스·타이머 (25·35편), 필요하면 tmux를 쓴다 |
[Detection] 사용자 로그아웃(세션 종료) 이후에도 실행 중인 해당 사용자 프로세스
↓
[확인] ps -eo pid,ppid,sid,user,tty,lstart,args -ww | awk '$2==1 && $5=="?"'
loginctl list-sessions ← 해당 SID 세션이 이미 종료되었는가
↓
[추적] cat /proc/PID/cgroup ← session-N.scope
journalctl -u sshd 에서 같은 시각 로그인 IP
find / -name nohup.out -newermt '-1 day' 2>/dev/null
↓
[판단] 업무용 배치인가? 등록되지 않은 도구인가?
↓
[Response] 소유자 확인 → 불명확하면 증거 수집 후 종료, 계정 점검
| 실수 | 결과 | 예방 |
|---|---|---|
cmd &만 하고 로그아웃 | 작업이 SIGHUP으로 종료 | nohup, setsid, 또는 systemd-run/tmux |
disown이면 안전하다고 믿음 | 환경에 따라 종료됨 (실측) | 확실한 방법은 nohup/setsid |
| nohup 작업의 출력 방치 | nohup.out이 무한히 커짐 | 출력 리다이렉션 지정 |
| 백그라운드 작업이 입력을 기다림 | SIGTTIN으로 멈춰 T 상태 | 입력을 < /dev/null로 |
| 장기 작업을 nohup으로 운영 | 재부팅 후 사라지고 관리·로그 없음 | systemd 서비스로 등록 |
[ ] jobs, bg, fg, kill %N 으로 작업 상태를 바꿔 봤다
[ ] job control 에서 작업마다 PGID 가 다른 것을 확인했다
[ ] SID, TPGID 로 세션과 포그라운드 그룹을 구분했다
[ ] nohup 프로세스의 SigIgn 에서 SIGHUP 무시 비트를 확인했다
[ ] 로그아웃 후 nohup·setsid 만 살아남는 것을 확인했다
[ ] 남은 프로세스의 SID 로 원래 세션을 추적할 수 있다
nohup(SIGHUP 무시)과 setsid(새 세션)는 살아남고, disown만으로는 보장되지 않는다.Part 2의 첫 글 「11. Linux Signal의 개념과 주요 시그널」 에서는 지금까지 여러 번 등장한 SIGHUP, SIGTERM, SIGKILL, SIGSTOP을 체계적으로 정리한다. 시그널 번호와 종료 코드 128+N 의 관계, 프로세스의 시그널 처리 상태(SigIgn, SigCgt)를 해석하는 방법을 실습한다.