infra
Platform

모듈 맵

[Linux] tmux로 터미널 접속이 끊겨도 백그라운드 작업 유지하는 법

0 / 37 완료

펼치기
0 / 37 완료0%

Linux · 08 / 37

[Linux] tmux로 터미널 접속이 끊겨도 백그라운드 작업 유지하는 법

SSH가 끊겨도 작업이 살아있다 — nohup, tmux, screen 완전 활용법

🚨INCIDENT ALERT
HIGH

SSH로 원격 서버에 접속해서 몇 시간짜리 DB 마이그레이션을 돌리고 있었는데, 인터넷이 끊기면서 터미널이 닫혔습니다. 작업은 어디 갔을까요? 처음 Linux를 다루는 엔지니어 대부분이 한 번씩 겪는 상황입니다.

nohup, disown, tmux를 이해하면 SSH가 끊겨도 프로세스가 살아있게 만들 수 있습니다. 장시간 배포, 대규모 마이그레이션, 새벽 점검 작업 — 이 모듈을 마치고 나면 터미널 세션 걱정 없이 작업을 맡길 수 있게 됩니다.

tmux & 백그라운드 세션 관리

이번 챕터에서 배울 것
  • 1포그라운드/백그라운드 프로세스 차이와 SIGHUP 동작 원리를 설명할 수 있다
  • 2nohup과 disown으로 SSH 끊김 이후에도 프로세스를 살릴 수 있다
  • 3tmux 세션·창·패널을 생성하고 분리(detach)·재접속(attach)을 수행할 수 있다
  • 4~/.tmux.conf로 prefix 키와 마우스 등 편의 기능을 설정할 수 있다
  • 5상황에 맞게 nohup과 tmux 중 적절한 도구를 선택해 적용할 수 있다
실습 환경 준비
tmux 설치 확인
tmux -V
tmux 없을 때 설치
sudo apt install tmux # Ubuntu/Debian sudo yum install tmux # RHEL/CentOS
실습용 원격 SSH 세션 준비

로컬 터미널 2개 열기 — 하나는 tmux 실행용, 하나는 attach 테스트용

💡개념

포그라운드 vs 백그라운드 프로세스

포그라운드·백그라운드·tmux 비교 — SSH 끊김 시 생존 여부

스크립트를 실행했더니 터미널이 점령당하고 다른 명령을 입력할 수 없는 상황이 있습니다. 백그라운드로 보내려고 Ctrl+Z를 눌렀는데 프로세스가 멈춰버리기도 하고, &로 뒤에 넣었더니 SSH가 끊겼을 때 같이 죽어버리기도 합니다. nohup, disown, tmux 같은 도구들이 왜 필요한지 이해하려면 먼저 포그라운드/백그라운드의 차이와 SIGHUP이 무엇인지를 알아야 합니다. 이 ConceptBlock에서는 터미널 세션과 프로세스의 관계를 살펴보고, 이후 실습에서 쓸 개념 기반을 갖춥니다.

터미널에서 명령을 실행할 때 프로세스는 두 가지 모드 중 하나로 동작합니다.

포그라운드(Foreground) 프로세스

터미널을 점유하고 있는 프로세스입니다. 명령을 입력하면 기본적으로 포그라운드에서 실행됩니다. 프로세스가 끝날 때까지 쉘 프롬프트가 돌아오지 않습니다.

로컬 터미널
# 포그라운드 실행 — 완료될 때까지 프롬프트가 돌아오지 않음
sleep 60
ping google.com
tar -czf backup.tar.gz /var/log

포그라운드 프로세스는 터미널과 직접 연결되어 있으므로, 터미널을 닫거나 SSH 세션이 끊기면 함께 종료됩니다.

백그라운드(Background) 프로세스

명령 끝에 &를 붙이면 백그라운드에서 실행됩니다. 쉘 프롬프트가 즉시 돌아오고, 다른 명령을 계속 입력할 수 있습니다.

로컬 터미널
# 백그라운드 실행 — 즉시 프롬프트 반환
sleep 60 &
# [1] 12345  ← [작업번호] PID

# 여러 개를 백그라운드로 보내기
./backup.sh &
./sync.sh &

백그라운드로 실행해도 현재 터미널 세션에 여전히 종속되어 있습니다. SSH가 끊기면 백그라운드 프로세스도 종료됩니다. 이것이 SIGHUP 문제입니다.

작업(Job) 상태 전환 요약:

명령 / 단축키동작
명령 &처음부터 백그라운드로 실행
Ctrl + Z포그라운드 프로세스를 일시정지(Stopped) 상태로 백그라운드 전환
bg %1일시정지된 작업 1번을 백그라운드에서 재개
fg %1작업 1번을 포그라운드로 가져오기
jobs현재 셸의 백그라운드/일시정지 작업 목록 확인

💡개념

SIGHUP 원리 — SSH가 끊기면 왜 프로세스가 죽는가

nohup python3 scraper.py &로 백그라운드로 보냈는데, SSH를 끊고 나중에 다시 접속해보니 프로세스가 죽어있습니다. &만으로는 충분하지 않기 때문입니다. SSH 세션이 끊길 때 터미널은 자식 프로세스에게 SIGHUP 신호를 전달하고, 대부분의 프로세스는 이 신호를 받으면 종료합니다. 이 흐름을 이해해야 nohup, disown, tmux 중 어떤 도구가 왜 효과적인지 판단할 수 있습니다.

SSH 세션이 끊길 때 무슨 일이 일어나는지 단계별로 추적해보겠습니다.

SSH 끊김과 SIGHUP 전파 — tmux 세션이 독립적으로 생존하는 이유

프로세스 계층 구조와 제어 터미널

sshd (PID 234)
  └── sshd: user@pts/0 (PID 891)   ← SSH 세션 관리
        └── bash (PID 892)          ← 셸 (세션 리더)
              ├── python job.py (PID 1001)   ← 포그라운드
              └── ./sync.sh & (PID 1002)     ← 백그라운드

bash세션 리더이자 **제어 터미널(pts/0)**을 소유합니다. 모든 하위 프로세스는 이 터미널을 공유합니다.

SSH 끊김 시 이벤트 순서:

  1. 네트워크 연결이 끊기거나 사용자가 터미널 창을 닫음
  2. SSH 서버(sshd)가 TCP 연결 종료를 감지
  3. 가상 터미널(pts/0)이 닫힘 (HangUp 발생)
  4. 커널이 세션 리더(bash, PID 892)에게 SIGHUP 시그널 전송
  5. bash가 SIGHUP을 받고, 자신이 관리하는 모든 작업(job)에게 SIGHUP을 전파
  6. python job.py./sync.sh 모두 SIGHUP을 받아 종료

SIGHUP의 역사적 맥락

SIGHUP(Signal HangUp, 시그널 번호 1)은 1970년대 전화 모뎀 시대의 유산입니다. 모뎀 연결이 끊어지는 것을 "HangUp"이라 불렀고, 이때 연결된 프로세스에게 종료를 알리는 시그널이 SIGHUP입니다. 현대에는 SSH 세션 종료나 터미널 닫기가 동일한 역할을 합니다.

SIGHUP의 이중적 의미

nginxsshd 같은 데몬은 SIGHUP을 "설정 파일 재로드" 신호로 재정의합니다. 같은 시그널이지만 수신 프로세스의 핸들러에 따라 동작이 달라집니다.

로컬 터미널
# nginx에서 SIGHUP은 종료가 아닌 설정 재로드
kill -HUP $(cat /var/run/nginx.pid)

# 일반 bash 스크립트는 SIGHUP을 받으면 종료

이 챕터에서 다루는 nohup, disown, tmux는 모두 자식 프로세스가 SIGHUP을 받지 않도록 차단하거나 터미널과의 연결을 끊는 방식으로 동작합니다.


기본 실습

실습 전 디렉토리와 예제 파일을 먼저 준비합니다.

로컬 터미널
# 실습 디렉토리 준비
mkdir -p /tmp/linux/part2/exam_5 && cd /tmp/linux/part2/exam_5

# 세션 실습용 스크립트 파일 생성
cat > /tmp/linux/part2/exam_5/monitor.sh << 'EOF'
#!/bin/bash
# tmux 세션 실습용 모니터링 스크립트
while true; do
  echo "$(date): CPU=$(top -bn1 | grep 'Cpu(s)' | awk '{print $2}')%"
  sleep 3
done
EOF
chmod +x /tmp/linux/part2/exam_5/monitor.sh

이제 실습을 진행합니다.

1& 백그라운드 실행, jobs, fg, bg 명령어

가장 기본적인 백그라운드 작업 관리를 실습합니다. jobs 명령으로 현재 셸의 작업 목록을 확인하고, fg/bg로 전환합니다.

로컬 터미널
# 1) 백그라운드로 sleep 실행
sleep 300 &
# 출력: [1] 15234
#       작업번호^  ^PID

# 두 번째 작업도 백그라운드로
sleep 400 &
# 출력: [2] 15235

# 2) 현재 셸의 작업 목록 확인
jobs
# 출력:
# [1]-  Running    sleep 300 &
# [2]+  Running    sleep 400 &
# +는 "현재 작업(current job)", -는 "이전 작업(previous job)"

# 3) 포그라운드에서 실행 중인 명령을 백그라운드로 보내기
ping google.com          # 포그라운드에서 실행
# (여기서 Ctrl+Z 입력)
# 출력: [3]+  Stopped   ping google.com

jobs
# 출력: [3]+  Stopped   ping google.com

# Stopped 상태에서 백그라운드로 재개
bg %3
# 출력: [3]+ ping google.com &

# 4) 백그라운드 작업을 포그라운드로 가져오기
fg %1           # 작업번호 1번을 포그라운드로
# fg             # %번호 없이 쓰면 현재 작업(+) 가져옴

# 5) 작업 종료
kill %2         # 작업번호 2번에 SIGTERM 전송
# 출력: [2]-  Terminated   sleep 400

주의: jobs는 현재 셸에서 시작한 작업만 보입니다. 다른 터미널에서 실행한 작업은 보이지 않습니다.

🔍실행 후 확인할 것
  • Ctrl-Z 후 jobs 실행 시 '[1]+ Stopped ...' 형태로 현재 중단된 작업이 표시된다
  • bg %1 실행 후 jobs 결과에서 해당 작업이 'Running'으로 바뀌어 백그라운드에서 실행 중인 것이 확인된다
  • fg %1 로 포그라운드로 가져오면 터미널 입력이 차단되고 프로세스 출력이 다시 보이기 시작한다
  • kill %2 로 작업을 종료하면 'Terminated' 메시지와 함께 jobs 목록에서 사라진다
2nohup으로 SSH 종료 후에도 살아남는 프로세스 실행

nohup은 "no hangup"의 약자로, SIGHUP 시그널을 무시한 채 프로세스를 실행합니다. SSH가 끊겨도 프로세스가 계속 실행됩니다.

로컬 터미널
# 기본 사용법
nohup ./script.sh &
# 출력: nohup: ignoring input and appending output to 'nohup.out'
# [1] 15678

# nohup.out에 stdout/stderr가 저장됨
tail -f nohup.out

# 출력 경로를 직접 지정
nohup ./script.sh > /var/log/myjob.log 2>&1 &

# stderr를 별도 파일로
nohup ./script.sh > /var/log/myjob.log 2> /var/log/myjob.err &

# 출력이 전혀 필요 없을 때 (조심해서 사용)
nohup ./script.sh > /dev/null 2>&1 &

nohup의 동작 원리:

  1. 자식 프로세스의 SIGHUP 핸들러를 "무시(SIG_IGN)"로 설정
  2. stdin을 /dev/null로 리다이렉트 (터미널 입력 불필요)
  3. stdout/stderr가 터미널이면 nohup.out으로 리다이렉트
로컬 터미널
# nohup.out 파일 실시간 모니터링
tail -f nohup.out

# 로그 파일에서 에러만 보기
grep -i error nohup.out

# 프로세스가 살아있는지 확인
ps aux | grep script.sh
pgrep -f script.sh

# 프로세스 종료 (PID로)
kill $(pgrep -f script.sh)

nohup vs &의 차이:

명령 &nohup 명령 &
SSH 끊김 시SIGHUP → 종료SIGHUP 무시 → 생존
출력 위치터미널nohup.out (또는 지정 파일)
stdin터미널 연결 유지/dev/null
3disown으로 이미 실행 중인 프로세스를 세션에서 분리

nohup 없이 이미 백그라운드로 돌리고 있던 프로세스를 뒤늦게 분리해야 할 때 disown을 씁니다. 셸의 작업 목록(job table)에서 제거하여 SSH가 끊겨도 SIGHUP이 전달되지 않습니다.

로컬 터미널
# 시나리오: nohup 없이 이미 백그라운드로 실행 중인 상황
./long_migration.sh &
# [1] 19234

jobs
# [1]+  Running   ./long_migration.sh &

# 이제 SSH가 끊길 것 같다. disown으로 분리
disown %1           # 작업번호 1번을 job table에서 제거
# 또는 PID로
disown 19234

# 확인: jobs 목록에서 사라짐
jobs
# (아무것도 출력되지 않음)

# 프로세스는 여전히 살아있음
ps aux | grep long_migration

disown 옵션:

로컬 터미널
# -h 옵션: job table에서 제거하지 않고 SIGHUP만 차단
disown -h %1

# 모든 작업을 한 번에 분리
disown -a

# -h와 -a 조합: 모든 작업에 SIGHUP 차단 적용
disown -ah

언제 disown을 쓰는가:

  • nohup을 깜빡하고 그냥 &로 실행했는데, 이미 오래 돌고 있는 경우
  • SSH 세션을 닫아야 하는데 작업을 멈추고 싶지 않은 경우
  • 실행 중인 상태에서는 nohup으로 전환이 불가능하므로 disown이 유일한 선택

disown의 한계: 프로세스의 stdout/stderr가 여전히 닫힌 터미널 파일 디스크립터를 가리킵니다. 출력이 있으면 "broken pipe" 에러가 발생할 수 있습니다. 가능하면 처음부터 nohup을 쓰거나, tmux를 사용하는 것이 안전합니다.

4tmux 기초 — 세션 생성, 분리, 재접속

tmux(Terminal MUltipleXer)는 터미널 멀티플렉서입니다. 단순히 SIGHUP을 막는 게 아니라, 서버에서 완전히 독립된 세션을 유지합니다. SSH가 끊겨도 tmux 세션은 서버에 그대로 남아있고, 다시 접속하면 나갔을 때 그 상태 그대로 돌아올 수 있습니다.

로컬 터미널
# tmux 설치 확인
tmux -V
# tmux 3.x.x

# 설치 (없는 경우)
sudo apt install tmux      # Ubuntu/Debian
sudo yum install tmux      # CentOS/RHEL
sudo dnf install tmux      # Fedora/RHEL 8+

세션 생성과 기본 사용:

로컬 터미널
# 1) 이름 없이 tmux 시작 (세션 이름은 숫자 0, 1, 2...)
tmux

# 2) 이름을 지정하고 시작 (권장)
tmux new -s migration
tmux new-session -s "db-backup"

# 3) tmux 세션 안에서 원하는 작업 실행
./run_migration.sh

# 4) 세션에서 분리 (detach) — 작업은 계속 돌면서 터미널로 돌아옴
# 단축키: Ctrl+b d
# 또는 명령으로:
tmux detach

세션 목록 확인 및 재접속:

로컬 터미널
# 실행 중인 tmux 세션 목록
tmux ls
tmux list-sessions
# 출력:
# migration: 1 windows (created Thu Mar 26 10:15:43 2026) [220x50]
# backup: 2 windows (created Thu Mar 26 09:30:12 2026) [220x50]

# 이름으로 재접속 (attach)
tmux attach -t migration
tmux a -t migration         # 축약형

# 세션이 하나뿐이면 이름 없이 재접속
tmux attach
tmux a

# 세션 종료 (안에서)
exit
# 또는
tmux kill-session -t migration

tmux의 핵심 장점:

  • SSH가 끊겨도 세션이 서버에 유지됨
  • 재접속 시 화면 상태까지 복원 (스크롤 히스토리, 실행 중인 명령 등)
  • 여러 터미널 창을 하나의 SSH 연결에서 관리
  • 팀원과 같은 tmux 세션에 동시 접속하여 작업 공유 가능
5tmux 핵심 단축키 — 패널 분할, 윈도우, 세션 관리

tmux의 모든 단축키는 prefix 키(기본값: Ctrl+b)를 먼저 누른 다음 명령 키를 입력합니다. Ctrl+b를 누르고 손을 뗀 뒤 명령 키를 누릅니다.

패널(Pane) 분할:

단축키              동작
─────────────────────────────────────────
Ctrl+b %           현재 패널을 좌우(수직)로 분할
Ctrl+b "           현재 패널을 위아래(수평)로 분할
Ctrl+b 방향키       패널 간 이동 (←↑↓→)
Ctrl+b o           다음 패널로 순환 이동
Ctrl+b z           현재 패널 전체화면 토글 (zoom)
Ctrl+b x           현재 패널 닫기 (확인 프롬프트)
Ctrl+b !           현재 패널을 새 윈도우로 분리
Ctrl+b { / }       패널 위치 교환 (왼쪽/오른쪽으로)
Ctrl+b Ctrl+방향키  패널 크기 조절 (1셀씩)

윈도우(Window) 관리:

단축키              동작
─────────────────────────────────────────
Ctrl+b c           새 윈도우 생성
Ctrl+b ,           현재 윈도우 이름 변경
Ctrl+b n           다음 윈도우로 이동
Ctrl+b p           이전 윈도우로 이동
Ctrl+b 0~9         번호로 윈도우 이동
Ctrl+b w           윈도우 목록을 트리뷰로 표시 및 선택
Ctrl+b &           현재 윈도우 닫기 (확인 프롬프트)

세션 관리:

단축키              동작
─────────────────────────────────────────
Ctrl+b d           세션에서 분리 (detach) — 가장 자주 씀
Ctrl+b s           세션 목록 표시 및 전환
Ctrl+b $           현재 세션 이름 변경
Ctrl+b (           이전 세션으로 전환
Ctrl+b )           다음 세션으로 전환

스크롤 및 복사 모드:

단축키              동작
─────────────────────────────────────────
Ctrl+b [           스크롤 모드 진입 (화살표/PgUp/PgDn으로 스크롤)
q 또는 Escape       스크롤 모드 종료
Ctrl+b ]           복사한 텍스트 붙여넣기

실용적인 tmux 사용 패턴:

로컬 터미널
# 실무에서 자주 쓰는 레이아웃 만들기
tmux new -s work          # work 세션 시작

# 안에서 패널 분할
# Ctrl+b %  → 좌우 분할
# 왼쪽: 편집기, 오른쪽에서:
# Ctrl+b "  → 위아래 분할
# 오른쪽 위: 로그 모니터링, 오른쪽 아래: 명령 입력

# 레이아웃 예시:
# ┌────────────┬────────────┐
# │            │  tail -f   │
# │   vim      │  app.log   │
# │            ├────────────┤
# │            │  $ _       │
# └────────────┴────────────┘

screen 기초 (레거시 서버)

일부 오래된 서버나 최소 설치 환경에서는 tmux가 없고 screen만 설치되어 있을 수 있습니다. tmux보다 오래된 도구이지만 동일한 핵심 기능을 제공합니다.

로컬 터미널
# screen 설치 확인
screen --version

# 새 세션 시작
screen
screen -S migration        # 이름 지정

# 세션에서 분리 (detach)
# Ctrl+a d  (screen의 prefix는 Ctrl+a)

# 세션 목록 확인
screen -ls
# 출력:
# There are screens on:
#   15234.migration (Detached)
#   15890.pts-0.server (Detached)

# 세션 재접속
screen -r migration
screen -r 15234            # PID로 재접속

# 세션 여러 개이고 하나만 있을 때
screen -r                  # 하나뿐이면 자동 접속

# screen 주요 단축키 (prefix: Ctrl+a)
# Ctrl+a c      새 창 생성
# Ctrl+a n      다음 창
# Ctrl+a p      이전 창
# Ctrl+a "      창 목록
# Ctrl+a d      detach
# Ctrl+a k      현재 창 종료
# Ctrl+a S      수평 분할 (소문자 s는 다른 기능)
# Ctrl+a Tab    분할 영역 간 이동

tmux vs screen 비교:

기능tmuxscreen
패널 분할강력하고 직관적제한적
설정 파일~/.tmux.conf~/.screenrc
스크롤백기본 지원기본 지원
플러그인 생태계풍부 (TPM)매우 제한적
현대 서버대부분 설치됨레거시에만 남음

레거시 서버가 아니라면 tmux를 쓰는 것이 좋습니다.


💼
실무 맥락실무 예제 — 수 시간짜리 DB 마이그레이션을 tmux로 안전하게 실행
현업 패턴

실제 프로덕션 환경에서 대규모 데이터베이스 마이그레이션을 실행하는 완전한 시나리오입니다.

상황: PostgreSQL 데이터베이스에서 3,000만 건 레코드를 마이그레이션해야 합니다. 예상 소요 시간은 4~6시간. 재택 근무 중이라 VPN이 불안정합니다.

단계 1: 작업 환경 구성

로컬 터미널
# 서버에 SSH 접속 후, 즉시 tmux 세션 생성
tmux new -s db-migration-20260326

# 세션 안에서 작업 디렉터리로 이동
cd /opt/migrations

# 로그 디렉터리 준비
mkdir -p /var/log/migration

단계 2: 모니터링 레이아웃 구성

로컬 터미널
# 현재 패널에서 수평 분할 (Ctrl+b ")
# 아래 패널에서 로그 모니터링 준비

# 위 패널: 마이그레이션 스크립트 실행 준비
# 아래 패널에서:
tail -f /var/log/migration/migrate.log

# 위 패널로 이동 (Ctrl+b ↑)

단계 3: 마이그레이션 실행

로컬 터미널
# 위 패널에서 스크립트 실행
./run_migration.sh 2>&1 | tee /var/log/migration/migrate.log

# 실행 중 확인 포인트:
# - 진행률 로그가 아래 패널에 흐르는지 확인
# - DB 연결 에러, 락 타임아웃 메시지 확인

단계 4: 작업 중 자리 비우기

로컬 터미널
# SSH를 닫아도 안전하게 분리
# Ctrl+b d  (detach)

# 터미널 창을 닫아도 됩니다.
# VPN이 끊겨도 됩니다.
# 퇴근해도 됩니다.

단계 5: 나중에 돌아와서 확인

로컬 터미널
# 서버에 다시 SSH 접속 후
tmux ls
# migration: 1 windows (created Thu Mar 26 10:15:43 2026) [220x50]

# 세션으로 돌아가기
tmux attach -t db-migration-20260326

# 마이그레이션이 완료됐거나 에러가 발생한 상태를 그대로 확인
# 로그도 그대로 아래 패널에 남아있음

단계 6: 완료 확인 및 정리

DB 클라이언트
# 레코드 수 검증
psql -d mydb -c "SELECT COUNT(*) FROM migrated_table;"

# 성공 시 세션 종료
exit           # 현재 패널 종료
# 또는
tmux kill-session -t db-migration-20260326

추가 팁: 알림 설정

장시간 작업이라면 완료 시 알림을 남겨두는 것도 좋습니다.

로컬 터미널
# 마이그레이션 완료 후 결과를 파일에 기록
./run_migration.sh 2>&1 | tee /var/log/migration/migrate.log
echo "완료: $(date)" >> /var/log/migration/migrate.log
echo "Exit code: $?" >> /var/log/migration/migrate.log

# 또는 간단한 완료 표시 (bell 신호)
./run_migration.sh && echo -e "\a마이그레이션 완료!"

이 패턴은 DB 마이그레이션뿐 아니라, 대규모 파일 처리, 모델 학습, 백업 작업 등 시간이 오래 걸리는 모든 작업에 동일하게 적용됩니다.


트러블슈팅

상황: SSH 재접속 후 tmux attach를 실행했더니 "no sessions" 에러가 나타납니다. 분명히 작업 중인 세션이 있었는데 목록에 보이지 않습니다.

원인: 서버가 재부팅됐거나, tmux 서버 프로세스가 크래시되었거나, 세션을 root로 만들었는데 일반 유저로 접속했을 때 발생합니다. tmux 소켓은 사용자별로 /tmp/tmux-<UID>/에 분리되어 있어 소유자가 다르면 보이지 않습니다.

진단: 소켓 파일과 프로세스를 직접 확인합니다.

로컬 터미널
# tmux 관련 프로세스 전체 확인
pgrep -a tmux
ps aux | grep tmux

# tmux 소켓 파일 위치 확인 (UID별로 분리됨)
ls /tmp/tmux-*/
# /tmp/tmux-1000/default   ← uid 1000의 소켓
# /tmp/tmux-0/default      ← root의 소켓

# root 세션이라면
sudo tmux ls

해결:

로컬 터미널
# root 소켓 접근 시
sudo tmux attach -t session-name

# 소켓 파일 직접 지정
tmux -S /tmp/tmux-1000/default ls
tmux -S /tmp/tmux-1000/default attach

# 다른 곳에서 이미 접속 중일 때 강제로 붙기
tmux attach -t migration -d   # -d: 기존 연결 끊고 단독 접속

# 세션이 정말 없다면 새로 생성 (작업이 살아있는지 별도 확인 필요)
tmux new -s ops

상황: nohup ./script.sh &로 백그라운드 실행 후 터미널을 닫았는데, 나중에 돌아와 보니 프로세스가 죽어있습니다. SSH 끊김을 막았는데도 종료된 상황은 여러 원인이 있습니다.

원인: SIGHUP 외에도 프로세스를 종료시키는 원인이 많습니다. OOM Killer(메모리 부족), 스크립트 자체 에러(exit code 0이 아님), 디스크 풀로 인한 쓰기 실패, systemd cgroup 제한 등이 대표적입니다.

진단: nohup.out과 커널 메시지를 순서대로 확인합니다.

로컬 터미널
# 1단계: nohup.out에서 에러 패턴 확인
tail -50 nohup.out
grep -i "error\|exception\|fatal\|killed\|oom" nohup.out

# 2단계: OOM Killer 여부 확인 (가장 흔한 원인)
dmesg | grep -i "killed process"
# Out of memory: Kill process 15234 (python) score 892 or sacrifice child

# 3단계: 디스크 상태 (로그 쓰기 실패 가능성)
df -h   # Use% 100%인 파티션 확인

# 4단계: systemd cgroup 제한 (SSH 세션을 통해 실행한 경우)
journalctl -xe | grep -i "killed"
systemctl status user-$(id -u).slice

해결:

로컬 터미널
# OOM이 원인이라면 — 메모리 확인 후 재실행
free -h
# 필요 시 swap 임시 추가 또는 다른 프로세스 종료 후 재실행
nohup ./script.sh > myjob.log 2>&1 &

# 스크립트 에러가 원인이라면 — set -e 여부 확인
head -5 ./script.sh  # set -euo pipefail 확인

# 중요한 장기 작업은 nohup 대신 tmux 안에서 실행
tmux new -s myjob
./script.sh 2>&1 | tee myjob.log
# Ctrl+b d 로 detach

상황: tmux 패널에서 실행한 프로그램이 hang 상태가 됐습니다. Ctrl+C를 여러 번 눌러도 반응이 없고, 터미널 자체가 굳은 것처럼 보입니다. 장기 실행 스크립트, 네트워크 I/O 대기, NFS 마운트 문제 등에서 자주 발생합니다.

원인: 프로세스가 SIGINT를 무시하거나, 커널 레벨 I/O 대기(D 상태) 중이거나, 네트워크 세션이 half-open 상태로 굳은 경우입니다. D 상태 프로세스는 SIGKILL조차 통하지 않습니다.

진단: 멈춘 프로세스의 상태를 다른 패널에서 확인합니다.

로컬 터미널
# 새 패널 열기 (Ctrl+b ") 후 프로세스 상태 확인
ps aux | grep 멈춘_프로세스명

# STAT 컬럼 확인: D = Disk Sleep (SIGKILL도 안 됨), S = 인터럽트 가능 대기
ps aux | awk '{print $8, $11}' | grep "^D"

# D 상태라면 원인 파악
dmesg | tail -20      # NFS 타임아웃, 디스크 에러 메시지 확인
iostat -x 1 3         # 디스크 I/O 상태

해결:

로컬 터미널
# 방법 1: Ctrl+\ (SIGQUIT) — Ctrl+C가 안 될 때
# Ctrl+\ 입력 → core dump 생성 후 종료되기도 함

# 방법 2: tmux에서 패널 강제 종료
# Ctrl+b x → "kill-pane? (y/n)" → y

# 방법 3: 다른 패널에서 직접 kill
pgrep -a python
kill -9 <PID>       # S 상태 프로세스는 종료됨

# 방법 4: SSH 자체가 굳었을 때 — SSH 이스케이프 시퀀스
# 줄 맨 앞에서 순서대로 입력: Enter → ~ → .
# 현재 SSH 연결 강제 종료 (서버 프로세스는 그대로 유지)
# 새 SSH 터미널에서 tmux attach로 재접속

# D 상태 프로세스는 해당 I/O 장치 문제 해결 전까지 제거 불가
# NFS 마운트 문제라면: sudo umount -l /mnt/nfs

tmux 설정 커스터마이징

기본 설정으로도 충분하지만, 자주 쓰다 보면 불편한 점이 생깁니다. ~/.tmux.conf 파일로 커스터마이즈합니다.

로컬 터미널
# ~/.tmux.conf — 실무에서 바로 쓸 수 있는 추천 설정

# prefix 키를 Ctrl+a로 변경 (screen 사용자에게 익숙)
# unbind C-b
# set -g prefix C-a
# bind C-a send-prefix

# 마우스 지원 활성화 (클릭으로 패널 선택, 스크롤 가능)
set -g mouse on

# 인덱스를 0이 아닌 1부터 시작 (키보드 숫자키 편의)
set -g base-index 1
setw -g pane-base-index 1

# 스크롤백 히스토리 늘리기 (기본 2000 → 10000줄)
set -g history-limit 10000

# 패널 분할 단축키를 직관적으로 변경
bind | split-window -h    # Ctrl+b |  → 좌우 분할
bind - split-window -v    # Ctrl+b -  → 위아래 분할

# vim 스타일로 패널 이동
bind h select-pane -L
bind j select-pane -D
bind k select-pane -U
bind l select-pane -R

# 설정 파일 즉시 재로드
bind r source-file ~/.tmux.conf \; display "설정 재로드 완료!"

# 상태바 스타일 (선택 사항)
set -g status-style bg=black,fg=white
set -g status-left "#[fg=green][#S] "
set -g status-right "#[fg=yellow]%Y-%m-%d %H:%M"
set -g status-right-length 50
로컬 터미널
# 설정 변경 후 적용 방법
# 1) 실행 중인 tmux 안에서:
tmux source-file ~/.tmux.conf

# 2) 또는 단축키로 (위 설정을 추가한 경우):
# Ctrl+b r

핵심 명령어 빠른 참조

nohup & jobs:

로컬 터미널
nohup ./script.sh &                     # SIGHUP 차단하여 백그라운드 실행
nohup ./script.sh > myjob.log 2>&1 &    # 로그 파일 지정
tail -f nohup.out                       # 기본 로그 모니터링
jobs                                    # 현재 셸 작업 목록
fg %1                                   # 작업 1번을 포그라운드로
bg %1                                   # 작업 1번을 백그라운드로 재개
disown %1                               # 작업 1번을 셸에서 분리
disown -h %1                            # 분리 없이 SIGHUP만 차단

tmux 세션 관리 (셸에서):

로컬 터미널
tmux new -s 이름                        # 이름 지정하여 새 세션 생성
tmux ls                                 # 세션 목록
tmux attach -t 이름                     # 세션 재접속
tmux attach -t 이름 -d                  # 다른 연결 끊고 재접속
tmux kill-session -t 이름              # 세션 강제 종료
tmux kill-server                        # 모든 세션과 tmux 서버 종료

tmux 단축키 (tmux 안에서, prefix = Ctrl+b):

Ctrl+b d          세션 분리 (detach)
Ctrl+b s          세션 목록/전환
Ctrl+b c          새 윈도우
Ctrl+b n/p        다음/이전 윈도우
Ctrl+b w          윈도우 목록
Ctrl+b %          좌우 패널 분할
Ctrl+b "          위아래 패널 분할
Ctrl+b 방향키      패널 이동
Ctrl+b z          패널 전체화면 토글
Ctrl+b x          현재 패널 닫기
Ctrl+b [          스크롤 모드 (q로 종료)
Ctrl+b ?          단축키 전체 목록
💡개념

표준 접속 패턴 — ssh -t host 'tmux new -A -s ops' (Bastion 포함)

Bastion 경유 tmux 표준 접속 패턴 — 재접속 시 세션 자동 복귀 흐름

긴급 장애 상황에서 다른 팀원이 어떤 tmux 세션에서 작업 중인지 파악이 안 됩니다. 세션 이름이 제각각이면 화면을 공유받거나 이어받는 데 시간이 걸립니다. Bastion 서버를 경유하는 환경에서는 tmux attachtmux new-session을 어떻게 조합하느냐에 따라 접속 흐름이 달라지기도 합니다. 접속 패턴을 ssh -t host 'tmux new -A -s ops' 한 줄로 표준화해두면, 항상 같은 이름의 세션에 자동 attach되고 없으면 새로 만들어집니다.

서버 접속 후 tmux 실행을 표준화하면 팀 전체 운영 일관성이 높아집니다.

기본 표준 접속 패턴:

SSH 접속 후
# 세션이 있으면 재접속, 없으면 새로 생성 (-A: attach-or-new)
ssh -t prod-server 'tmux new -A -s ops'

# ~/.ssh/config에 등록해 단축 사용
# Host prod
#     HostName 10.0.0.100
#     User ubuntu
#     IdentityFile ~/.ssh/prod.pem
#     RemoteCommand tmux new -A -s ops
#     RequestTTY force  # -t 옵션 자동 적용

ssh prod  # → 자동으로 tmux ops 세션에 접속

Bastion 경유 접속:

SSH 접속 후
# Bastion → 내부 서버 → tmux 세션
ssh -J bastion.example.com -t app-server 'tmux new -A -s ops'

# ProxyJump를 ~/.ssh/config에 등록
# Host app-internal
#     HostName 10.0.1.100
#     User ubuntu
#     ProxyJump bastion.example.com
#     RemoteCommand tmux new -A -s ops
#     RequestTTY force

ssh app-internal  # → Bastion 경유 후 tmux 세션 자동 접속

운영 표준화 이점:

  • 팀원 누구든 같은 세션명으로 접속 → 작업 인수인계 용이
  • 네트워크 끊겨도 세션 유지 → 장애 대응 중 재접속 즉시 가능
  • -A 플래그로 실수로 세션 중복 생성 방지
💡개념

세션 로그·스크롤백·캡처 — 장애 기록 남기기

tmux 로깅 3가지 방법 — script / pipe-pane / capture-pane 비교

새벽 장애 대응 중 tmux 창에서 한참 명령어를 실행했는데, 아침에 복기하려니 스크롤 버퍼가 넘쳐서 초반 출력이 사라졌습니다. 인시던트 보고서를 써야 하는데 "그때 뭘 입력했고 어떤 결과가 나왔는지"를 기억에만 의존해야 하는 상황입니다. tmux는 pipe-pane으로 패널 출력을 파일에 실시간 저장할 수 있고, capture-pane으로 현재 화면을 스냅샷처럼 저장할 수 있습니다. 장애 대응 전에 로깅을 켜두는 습관이 재현과 보고 모두 편하게 만듭니다.

장애 대응 중 실행한 명령과 출력을 나중에 재현하거나 보고서에 활용할 수 있도록 로깅합니다.

pipe-pane — 패널 출력을 파일로 저장:

로컬 터미널
# 현재 패널 출력을 파일에 저장 시작
# tmux 내에서 실행 (또는 Ctrl+b :로 명령 모드)
tmux pipe-pane -o 'cat >> /tmp/session-$(date +%Y%m%d-%H%M%S).log'

# 저장 중지
tmux pipe-pane

# 빠른 단축키 등록 (~/.tmux.conf)
# bind L pipe-pane -o 'cat >> ~/tmux-logs/session-#S-#I-#P-$(date +%Y%m%d-%H%M%S).log'
# Ctrl+b L로 로깅 시작/중지 토글

script — 세션 전체 녹화:

로컬 터미널
# 세션 시작 시 전체 녹화 (tmux 외부에서)
script -f /tmp/incident-$(date +%Y%m%d-%H%M%S).log
# 명령 실행...
# 종료: exit 또는 Ctrl+D

# 재생 (scriptreplay)
scriptreplay --timing=/tmp/incident.timing /tmp/incident.log

스크롤백 버퍼 캡처:

로컬 터미널
# 현재 패널의 스크롤백 전체를 파일로 저장
tmux capture-pane -p -S -  # 터미널에 출력
tmux capture-pane -p -S - > /tmp/pane-dump-$(date +%Y%m%d).txt  # 파일 저장

# 다른 패널 내용 캡처 (패널 ID 지정)
tmux capture-pane -t 0:0.1 -p > /tmp/pane1.txt

# ~/.tmux.conf에 단축키 등록
# bind P capture-pane -p -S - | tee /tmp/pane-$(date +%Y%m%d-%H%M%S).txt
💡개념

세션 복구 — tmux-resurrect·스크립트 기반 영속화

tmux-resurrect 저장·복구 사이클 — prefix+Ctrl-s 저장, 재부팅 후 prefix+Ctrl-r 복구

서버 재부팅 시 tmux 세션이 사라집니다. 중요한 세션 레이아웃은 영속화해 두면 복구 시간이 줄어듭니다.

tmux-resurrect (플러그인 방식):

로컬 터미널
# TPM(Tmux Plugin Manager) 설치
git clone https://github.com/tmux-plugins/tpm ~/.tmux/plugins/tpm

# ~/.tmux.conf에 추가
# set -g @plugin 'tmux-plugins/tpm'
# set -g @plugin 'tmux-plugins/tmux-resurrect'
# run '~/.tmux/plugins/tpm/tpm'

# 플러그인 설치
tmux source ~/.tmux.conf
# tmux 내에서: Ctrl+b I (대문자 i)

# 세션 저장: Ctrl+b Ctrl+s
# 세션 복구: Ctrl+b Ctrl+r

스크립트 기반 세션 재생성 (플러그인 없이):

로컬 터미널
# /opt/scripts/start-ops-session.sh
#!/bin/bash
# 장애 대응용 표준 tmux 세션 생성
SESSION="ops"

# 세션이 이미 있으면 재접속
tmux has-session -t $SESSION 2>/dev/null && tmux attach -t $SESSION && exit

# 새 세션 생성
tmux new-session -d -s $SESSION -n "monitor"
tmux send-keys -t $SESSION:monitor "watch -n 1 'uptime; free -h; df -h /'" C-m

# 창 추가
tmux new-window -t $SESSION -n "logs"
tmux send-keys -t $SESSION:logs "tail -f /var/log/syslog" C-m

tmux new-window -t $SESSION -n "shell"

# 세션 접속
tmux attach -t $SESSION

다음 모듈에서는 환경 변수(Environment Variables)를 다룹니다 — PATH, 셸 변수, .bashrc.bash_profile의 차이, 그리고 비밀 키를 환경 변수로 안전하게 전달하는 방법을 배웁니다.

프로덕션 환경에서 시간이 오래 걸리는 작업(마이그레이션, 백업, 배치 처리, 모델 학습)은 항상 tmux 안에서 실행하는 습관을 들이는 것을 권장합니다. 몇 분 투자해서 tmux 세션을 만드는 것이, 몇 시간 후에 작업이 죽어있는 것을 발견하는 것보다 훨씬 낫습니다.

지식 확인

퀴즈 — 5문제

Q1

tmux에서 현재 세션을 분리(detach)하여 백그라운드로 보내는 단축키는?

Q2

이름이 'deploy'인 tmux 세션에 재접속하는 올바른 명령은?

Q3

tmux에서 현재 창(window)을 좌우로 분할하여 두 개의 패널을 만드는 단축키는?

Q4

tmux에서 새 창(window)을 생성하는 단축키와, 창 목록을 보여주는 단축키의 올바른 조합은?

Q5

~/.tmux.conf에서 prefix 키를 Ctrl+b에서 Ctrl+a로 변경하는 올바른 설정은?

0 / 5 답변

🧪 실습으로 확인하기

새 서버 인수인계 — 처음 30분

초급

낯선 Linux 서버를 인수받았을 때 OS, 서비스, 로그를 빠르게 파악하는 루틴을 직접 수행한다.

30📋 3단계💻 직접 환경
실습 시작하기 →

이것도 배워보세요

linux중급 · 60
[Linux] 운영 서버 프로세스 이상 진단: 좀비, 메모리 누수, CPU 과점유 실전 대응
Linux 트랙 계속
docker입문 · 30
[Docker] 백엔드 개발자에게 Docker와 컨테이너 가상화가 필수인 이유
Docker 트랙 시작점