Split View: 리눅스 장애 대응 명령어 완전 가이드: 첫 60초부터 근본 원인까지
리눅스 장애 대응 명령어 완전 가이드: 첫 60초부터 근본 원인까지
- 들어가며
- 1. 첫 60초 — 표준 점검 시퀀스
- 2. 부하 평균이 실제로 말해 주는 것
- 3. CPU를 누가 쓰고 있는가
- 4. 메모리 — free의 어느 숫자를 믿을 것인가
- 5. 디스크가 찼을 때 — 용량과 inode
- 6. I/O — 느린 디스크의 증거 찾기
- 7. 네트워크 — 포트, 큐, 그리고 재전송
- 8. 로그 — 시간 범위를 먼저 좁힌다
- 9. 멈춘 것처럼 보이는 프로세스 파헤치기
- 10. 재현되지 않는 장애를 잡는 상시 수집
- 퀴즈: 실력을 확인해 보세요
- 마치며
- 참고 자료
- 이어서 읽기
들어가며
장애 대응에서 가장 많이 낭비되는 시간은 명령어를 몰라서 생기지 않습니다. 어떤 명령을 어떤 순서로 쳐야 하는지 모르기 때문에 생깁니다. top을 띄우고 30초간 바라보다가, df를 치고, 다시 top으로 돌아오고, 그러는 동안 5분이 지나갑니다.
이 글은 "서버가 느리다", "응답이 없다", "배포 후 이상하다"는 연락을 받은 순간부터 실행할 명령을 순서대로 정리합니다. 이 블로그에는 이미 리눅스 성능 엔지니어링 가이드가 있지만 그 글은 프로파일링과 튜닝 방법론을 다룹니다. 이 글은 다릅니다. 아직 무엇이 문제인지 모르는 상태에서 후보를 하나씩 배제해 나가는 순서가 주제입니다.
전제는 하나입니다. 진단은 "범인 찾기"가 아니라 "용의자 제외하기" 입니다. CPU가 아니면 CPU를 지우고, 메모리가 아니면 메모리를 지웁니다. 남은 것이 답입니다.
배포판 표기는 다음과 같습니다. RHEL 계열은 RHEL/Rocky/AlmaLinux 8 이상, Debian 계열은 Debian 12 및 Ubuntu 22.04 이상을 기준으로 합니다.
1. 첫 60초 — 표준 점검 시퀀스
Netflix 성능 엔지니어링 팀이 공개한 "첫 60초" 체크리스트가 사실상 업계 표준이 되었습니다. 핵심은 한 번에 열 개를 던져 놓고 전체 그림을 먼저 본 다음 깊게 파는 것입니다.
uptime
dmesg --level=err,warn --ctime | tail -20
vmstat 1 5
mpstat -P ALL 1 3
pidstat 1 3
iostat -xz 1 3
free -m
sar -n DEV 1 3
ss -s
각 명령이 배제하는 용의자는 다음과 같습니다.
| 명령 | 답하는 질문 | 이것으로 배제되는 것 |
|---|---|---|
uptime | 부하가 지금 오르는 중인가, 내려가는 중인가 | 이미 끝난 장애 |
dmesg | 커널이 뭔가 죽였거나 끊었는가 | OOM Kill, 디스크 오류, 링크 다운 |
vmstat 1 | CPU 대기인가 I/O 대기인가 스왑인가 | 세 가지 중 두 가지 |
mpstat -P ALL | 전체가 바쁜가 코어 하나만 바쁜가 | 단일 스레드 병목 |
pidstat 1 | 어느 프로세스가 쓰고 있는가 | 무고한 프로세스 전부 |
iostat -xz 1 | 디스크가 밀리고 있는가 | 스토리지 |
free -m | 메모리가 실제로 부족한가 | 메모리 |
sar -n DEV | 대역폭이 포화인가 | 네트워크 처리량 |
ss -s | 소켓이 새고 있는가 | 커넥션 누수 |
mpstat, pidstat, iostat, sar는 모두 sysstat 패키지에 들어 있습니다. 기본 설치가 아닌 경우가 많으므로 서버를 만들 때 미리 넣어 두세요.
# RHEL 계열
sudo dnf install -y sysstat
# Debian 계열
sudo apt install -y sysstat
sar로 과거 데이터를 보려면 수집 데몬이 켜져 있어야 합니다. Debian 계열은 /etc/default/sysstat에서 활성화 값을 켜야 수집이 시작됩니다.
sudo systemctl enable --now sysstat
2. 부하 평균이 실제로 말해 주는 것
uptime의 세 숫자는 1분, 5분, 15분 부하 평균입니다. 여기서 두 가지를 오해하기 쉽습니다.
첫째, 리눅스의 부하 평균은 CPU 대기만 세지 않습니다. 실행 가능(R) 상태뿐 아니라 중단 불가 대기(D 상태) 프로세스도 포함합니다. D 상태는 대부분 디스크 I/O 또는 NFS 응답 대기입니다. 그래서 CPU가 놀고 있는데도 부하가 40이 나오는 상황이 생깁니다. 이 경우 범인은 CPU가 아니라 스토리지입니다.
둘째, 절대값보다 기울기가 중요합니다. 1분 값이 15분 값보다 크면 지금 악화되는 중이고, 작으면 이미 회복 중입니다. 후자라면 급하게 재기동할 이유가 없습니다.
uptime
14:22:31 up 41 days, 3:11, 2 users, load average: 12.44, 6.80, 3.15
이 출력은 "지난 15분 사이에 부하가 4배로 뛰었고 지금도 오르는 중"이라는 뜻입니다. 코어 수와 비교해야 의미가 생기므로 코어 수를 먼저 확인하세요.
nproc
lscpu | grep -E '^CPU\(s\)|Thread|Core|Socket|Model name'
3. CPU를 누가 쓰고 있는가
vmstat 1의 첫 줄은 부팅 이후 평균이므로 무시하고 둘째 줄부터 읽습니다. 이는 man 페이지에 명시된 동작입니다.
vmstat 1 5
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
8 1 0 210488 91232 3120440 0 0 12 34 980 2210 71 14 9 6 0
읽는 순서는 이렇습니다.
r: 실행 대기 중인 프로세스 수. 코어 수보다 지속적으로 크면 CPU 포화입니다.b: I/O 완료를 기다리며 블록된 프로세스 수. 여기가 크면 스토리지를 의심합니다.si/so: 스왑 인/아웃. 0이 아니면 메모리 부족이 이미 성능에 영향을 주고 있습니다.wa: I/O 대기 시간 비율. 높으면 디스크, 낮은데 느리면 디스크가 아닙니다.st: 하이퍼바이저에 빼앗긴 시간. 클라우드 VM에서 이 값이 계속 5를 넘으면 호스트가 과밀한 것이고, 내 서버 안에서는 해결할 수 없습니다.
전체가 바쁜지 코어 하나만 바쁜지는 mpstat으로 가릅니다.
mpstat -P ALL 1 3
코어 하나만 100퍼센트라면 단일 스레드 병목입니다. 스케일 업으로 해결되지 않고 코드나 설정을 봐야 합니다.
범인 프로세스는 pidstat으로 찾습니다. top과 달리 누적이 아니라 구간별 값을 계속 찍어 주기 때문에 로그로 남기기 좋습니다.
pidstat -u 1 5
pidstat -t -p 1234 1 5
-t 옵션은 스레드 단위로 분해합니다. 자바나 Go 프로세스처럼 스레드가 많은 경우 어느 스레드가 도는지 볼 수 있습니다. %wait는 실행 준비가 되었는데 CPU를 못 받아 기다린 비율이라, 여기가 높으면 프로세스가 느린 게 아니라 CPU 경합이 심한 것입니다.
전통적인 ps 조합도 여전히 유용합니다.
ps -eo pid,ppid,stat,pcpu,pmem,etime,rss,args --sort=-pcpu | head -15
stat 열에서 D가 보이면 중단 불가 대기, Z는 좀비, T는 정지 상태입니다.
4. 메모리 — free의 어느 숫자를 믿을 것인가
free -m에서 초보가 가장 자주 오독하는 곳이 free 열입니다. 리눅스는 남는 메모리를 페이지 캐시로 씁니다. free가 작은 것은 정상이며, 봐야 할 곳은 available 열입니다.
free -m
total used free shared buff/cache available
Mem: 15884 10233 412 188 5238 5102
Swap: 4095 120 3975
available은 "지금 새 프로세스가 요청하면 회수해서 줄 수 있는 양"의 커널 추정치입니다. 이 값이 총량 대비 충분히 남아 있으면 메모리 부족이 아닙니다.
메모리 부족이 의심되면 커널이 무엇을 죽였는지부터 확인합니다.
dmesg --ctime | grep -i -E 'out of memory|oom-kill|killed process'
journalctl -k --since '2 hours ago' | grep -i oom
dmesg --ctime(-T)은 사람이 읽는 시각으로 바꿔 주지만, man 페이지는 시스템 서스펜드/리줌 이후 타임스탬프가 부정확할 수 있다고 경고합니다. 정밀한 시각이 필요하면 journalctl -k를 쓰세요.
세부 분해는 /proc/meminfo를 봅니다.
grep -E 'MemAvailable|Dirty|Writeback|Slab|SReclaimable|Committed_AS' /proc/meminfo
Dirty가 크고 줄지 않으면 쓰기가 디스크로 못 내려가는 중입니다. Slab이 비정상적으로 크면 커널 객체 누수를 의심합니다. 프로세스별 실제 사용량은 RSS가 공유 페이지를 중복 계산하므로, 가능하면 PSS를 보세요.
sudo grep -H '^Pss:' /proc/1234/smaps_rollup
5. 디스크가 찼을 때 — 용량과 inode
디스크 부족은 두 종류입니다. 블록 부족과 inode 부족입니다. df -h만 보고 "여유 있는데요"라고 답했다가 실제로는 inode가 고갈된 경우가 드물지 않습니다.
df -h
df -i
df -i에서 IUse%가 100퍼센트면 용량이 남아 있어도 새 파일을 만들 수 없습니다. 세션 파일이나 메일 큐처럼 작은 파일이 수백만 개 쌓이는 디렉터리가 원인인 경우가 대부분입니다.
큰 디렉터리를 찾을 때는 한 파일시스템 안에서만 재귀하도록 제한하는 것이 안전합니다.
sudo du -x -h --max-depth=1 /var | sort -h | tail -20
-x는 다른 파일시스템으로 넘어가지 않게 합니다. 이것이 없으면 /proc이나 네트워크 마운트까지 훑다가 한참 걸립니다.
df와 du의 값이 크게 다르다면 삭제되었지만 아직 열려 있는 파일 때문입니다. 파일을 지워도 그 파일을 연 프로세스가 살아 있으면 공간이 반환되지 않습니다.
sudo lsof +L1
sudo lsof -nP +L1 | awk '{print $1, $2, $7, $9}' | sort -k3 -n -r | head
+L1은 링크 수가 1보다 작은, 즉 이미 unlink된 열린 파일을 나열합니다. 해결책은 해당 프로세스에 로그 재오픈 시그널을 보내거나 재기동하는 것입니다. 자세한 원리는 이 시리즈의 파일 디스크립터와 inode 가이드에서 다룹니다.
6. I/O — 느린 디스크의 증거 찾기
iostat -xz 1 3
-x는 확장 통계, -z는 활동 없는 장치를 생략합니다. 첫 리포트는 부팅 이후 통계이므로 -y로 생략하거나 둘째 리포트부터 읽습니다.
봐야 할 열은 다음과 같습니다.
r_await/w_await: 요청이 큐에서 기다린 시간까지 포함한 평균 응답 시간(밀리초). NVMe에서 한 자리 밀리초를 넘으면 이상하고, 회전 디스크는 10에서 20까지는 정상 범위입니다.aqu-sz: 평균 큐 길이. 1을 크게 넘으면 장치가 요청을 소화하지 못하는 중입니다.rareq-sz/wareq-sz: 요청 평균 크기(KiB). 작은 랜덤 I/O인지 큰 순차 I/O인지 구분해 줍니다.%util: 이 장치에 I/O가 발행되어 있던 시간 비율.
%util은 반드시 조심해야 합니다. man 페이지가 명시적으로 경고합니다. 요청을 직렬로 처리하는 장치에서는 100퍼센트가 포화를 뜻하지만, RAID 배열이나 최신 SSD처럼 병렬 처리하는 장치에서는 100퍼센트여도 성능 한계가 아닙니다. 이 경우 판단 기준은 %util이 아니라 await입니다.
어떤 프로세스가 I/O를 만드는지는 pidstat -d로 봅니다.
pidstat -d 1 5
kB_rd/s, kB_wr/s가 프로세스별 읽기·쓰기, iodelay는 블록 I/O 지연을 클럭 틱 단위로 보여 줍니다. iotop이 설치되어 있다면 대화형으로 보기 더 편합니다.
sudo iotop -oPa
7. 네트워크 — 포트, 큐, 그리고 재전송
netstat은 net-tools 패키지의 유물이고, 최신 배포판의 표준은 ss입니다.
ss -tulpn
ss -tan state established | head
ss -s
-tTCP,-uUDP,-l리스닝만,-a전부,-n이름 해석 안 함,-p소켓을 쓰는 프로세스 표시.-p로 프로세스명을 보려면 보통 root 권한이 필요합니다.
Recv-Q와 Send-Q는 소켓 상태에 따라 뜻이 달라집니다. 리스닝 소켓에서 Recv-Q는 accept 대기 중인 완료 큐의 길이이고, Send-Q는 백로그 최대치입니다. 리스닝 소켓의 Recv-Q가 계속 차 있으면 애플리케이션이 accept를 못 따라가는 중입니다. 확립된 연결에서는 각각 아직 읽히지 않은 수신 데이터와 아직 확인되지 않은 송신 데이터입니다. 다만 이 두 열의 의미는 man7.org에 공개된 ss 매뉴얼 본문에 명시되어 있지 않고 iproute2 구현 동작에 근거합니다. 판단의 근거로 삼기 전에 설치된 iproute2 버전의 man 페이지에서 한 번 확인하는 편이 안전합니다.
경로 문제인지 애플리케이션 문제인지는 계층을 나눠 확인합니다.
ip -brief addr
ip route get 10.0.3.14
ping -c 4 10.0.3.14
mtr -rwc 20 10.0.3.14
curl -sS -o /dev/null -w 'dns:%{time_namelookup} conn:%{time_connect} tls:%{time_appconnect} ttfb:%{time_starttransfer} total:%{time_total}\n' https://example.com
curl의 타이밍 변수는 어느 단계가 느린지 한 줄로 보여 줍니다. DNS 단계가 느리면 이름 해석 문제이고, time_connect가 느리면 경로나 방화벽, time_starttransfer만 느리면 서버 애플리케이션입니다.
TCP 재전송이 많은지 확인합니다.
nstat -az | grep -i -E 'retrans|drop|overflow'
ss -ti state established | grep -E 'retrans|rtt' | head
ListenOverflows 또는 ListenDrops가 증가하고 있다면 백로그가 넘치는 중입니다.
8. 로그 — 시간 범위를 먼저 좁힌다
로그를 처음부터 읽지 마세요. 장애 시각의 앞뒤 5분만 봅니다.
journalctl --since '2026-08-15 14:10' --until '2026-08-15 14:30' -p err
journalctl -u nginx.service -n 200 --no-pager
journalctl -u nginx.service -f
journalctl -k -b -1 -p warning
journalctl -g 'timeout|refused|denied' --since today
-p err는 err 이상 우선순위만 봅니다. 우선순위 이름은 emerg, alert, crit, err, warning, notice, info, debug 순입니다.-b -1은 직전 부팅의 로그입니다. 서버가 갑자기 재부팅된 경우 여기에 원인이 남아 있습니다.-g는 MESSAGE 필드에 정규식을 적용합니다.
부팅 목록과 재부팅 원인 확인은 이렇게 합니다.
journalctl --list-boots
last -x reboot shutdown | head
last -x에 정상 종료 기록 없이 재부팅만 있으면 커널 패닉이나 전원 문제, 하이퍼바이저 강제 재시작을 의심합니다. 로그 파이프라인 자체를 설계하는 방법은 리눅스 로그 운영 가이드에서 다룹니다.
9. 멈춘 것처럼 보이는 프로세스 파헤치기
응답이 없는 프로세스는 세 가지 중 하나입니다. CPU를 태우는 중, 무언가를 기다리는 중, 락에 걸린 중입니다.
먼저 커널이 보고하는 대기 지점을 봅니다. 이것은 부작용이 없는 읽기 연산입니다.
cat /proc/1234/status | grep -E 'State|Threads|voluntary'
sudo cat /proc/1234/stack
sudo cat /proc/1234/wchan; echo
ls -l /proc/1234/fd | head
State가 D (disk sleep)이면 중단 불가 대기입니다. 이 상태의 프로세스는 SIGKILL로도 죽지 않습니다. 기다리는 자원(대부분 스토리지나 NFS)이 응답해야 풀립니다.
시스템 콜 수준을 봐야 한다면 strace를 붙이되, 운영 환경에서 프로세스를 크게 느리게 만든다는 점을 반드시 인지하고 짧게 사용하세요.
sudo strace -f -p 1234 -tt -T -e trace=network,file 2>&1 | head -50
sudo strace -c -f -p 1234
-c는 요약만 내므로 부하가 상대적으로 적습니다. 어느 시스템 콜에서 시간을 쓰는지 한눈에 보여 줍니다. strace가 붙지 않는다면 커널의 ptrace 제한 설정 때문일 수 있습니다.
cat /proc/sys/kernel/yama/ptrace_scope
값이 1 이상이면 같은 사용자라도 임의 프로세스에 붙을 수 없고 root가 필요합니다.
파일 잠금 때문에 멈춘 경우도 흔합니다.
cat /proc/locks | head
sudo lsof /var/lib/myapp/data.db
10. 재현되지 않는 장애를 잡는 상시 수집
가장 흔한 실패는 "그때 로그를 안 남겼다"입니다. 순간 부하는 사람이 로그인하기 전에 끝나므로, 평상시에 수집이 돌고 있어야 합니다.
sar는 이 용도로 만들어졌습니다. 기본 수집 주기는 배포판 설정에 따라 다르며 보통 10분 간격입니다.
sar -u -f /var/log/sa/sa15
sar -r -s 14:00:00 -e 15:00:00
sar -n DEV -s 14:00:00 -e 15:00:00
sar -q
-f로 특정 날짜 파일을 지정합니다. 파일 경로는 RHEL 계열이 /var/log/sa/, Debian 계열이 /var/log/sysstat/으로 다릅니다.
임계치를 넘을 때만 스냅샷을 남기는 간단한 방식도 유효합니다. 다음은 부하가 임계치를 넘으면 프로세스 목록을 파일로 남기는 예입니다.
#!/usr/bin/env bash
set -euo pipefail
THRESHOLD=20
LOAD=$(awk '{print int($1)}' /proc/loadavg)
if [ "$LOAD" -ge "$THRESHOLD" ]; then
TS=$(date +%Y%m%d-%H%M%S)
OUT="/var/log/spike-$TS.txt"
{
uptime
ps -eo pid,stat,pcpu,pmem,etime,args --sort=-pcpu | head -30
ss -s
vmstat 1 3
} > "$OUT"
fi
이 스크립트를 1분 주기 cron이나 systemd 타이머에 걸어 두면, 다음번 순간 부하 때 사람이 없어도 증거가 남습니다. 타이머 작성법은 systemd 타이머 완전 정복을 참고하세요.
마지막으로, 진단 중 실행하는 명령이 장애를 키우지 않도록 주의합니다. 루트에서 du -x를 빼먹고 돌리거나, 이미 I/O가 포화된 디스크에 find / 전체 스캔을 거는 것은 상황을 악화시킵니다. 디스크가 이미 100퍼센트 사용 중일 때는 읽기 작업도 줄을 서야 한다는 점을 기억하세요.
퀴즈: 실력을 확인해 보세요
퀴즈 1: 부하 평균이 40인데 top의 CPU 사용률은 전부 합쳐 15퍼센트입니다. 무엇을 먼저 확인해야 할까요?
정답: 중단 불가 대기(D 상태) 프로세스와 디스크 I/O를 확인합니다
설명: 리눅스의 부하 평균은 실행 가능(R) 상태뿐 아니라 중단 불가 대기(D) 상태 프로세스도 포함합니다. CPU가 한가한데 부하만 높다면 대부분 스토리지나 NFS 응답 대기가 원인입니다. 순서는 다음과 같습니다.
ps -eo pid,stat,wchan:20,args --sort=-pcpu | awk '$2 ~ /D/'
iostat -xz 1 3
vmstat 1 5
iostat의 await가 크고 vmstat의 b 열이 크다면 스토리지 병목이 확정됩니다.
퀴즈 2: df -h는 여유가 30퍼센트인데 애플리케이션이 파일을 만들지 못하고 실패합니다. 원인 후보 두 가지는?
정답: inode 고갈, 그리고 해당 경로가 실제로는 다른(가득 찬) 파일시스템이거나 쿼터에 걸린 경우입니다
설명: 블록 여유와 inode 여유는 별개입니다. 작은 파일이 수백만 개 쌓이면 용량이 남아도 inode가 먼저 바닥납니다.
df -i
df -h /var/lib/myapp
mount | grep myapp
df에 경로를 직접 넘겨 그 경로가 어느 파일시스템에 속하는지부터 확인하는 것이 중요합니다. /var가 별도 마운트인 경우가 흔합니다.
퀴즈 3: 로그 파일을 지웠는데 df의 사용량이 그대로입니다. 왜이고, 어떻게 해결하나요?
정답: 파일을 연 프로세스가 아직 살아 있어 inode가 해제되지 않았기 때문입니다. 해당 프로세스가 파일을 다시 열게 해야 공간이 반환됩니다
설명: unlink는 디렉터리 엔트리만 지웁니다. 열린 파일 디스크립터가 남아 있는 한 데이터 블록은 유지됩니다.
sudo lsof -nP +L1 | head
sudo systemctl reload rsyslog
로그의 경우 대개 재오픈을 유발하는 reload로 충분합니다. 프로세스를 죽이는 것은 마지막 수단입니다. 애초에 이런 상황을 만들지 않으려면 logrotate의 copytruncate 대신 postrotate 훅에서 재오픈 신호를 보내도록 설정하세요.
퀴즈 4: iostat에서 %util이 100퍼센트로 나옵니다. 디스크가 포화라고 결론지어도 될까요?
정답: 아닙니다. 요청을 병렬 처리하는 장치에서는 100퍼센트여도 한계가 아닙니다
설명: iostat man 페이지는 "RAID 배열이나 최신 SSD처럼 요청을 병렬로 처리하는 장치에서는 이 값이 성능 한계를 반영하지 않는다"고 명시합니다. %util은 "이 장치에 요청이 하나라도 발행되어 있던 시간의 비율"일 뿐입니다. 판단은 r_await, w_await, aqu-sz로 해야 합니다. 응답 시간이 평소 수준이라면 100퍼센트여도 문제가 아닙니다.
퀴즈 5: 프로세스에 SIGKILL을 보냈는데 죽지 않습니다. 가능한 상황과 확인 방법은?
정답: 프로세스가 D 상태(중단 불가 대기)이거나 이미 좀비(Z)입니다
설명: SIGKILL은 커널이 강제로 처리하지만, D 상태에서는 시그널 전달 자체가 지연됩니다. 커널이 기다리는 자원(스토리지, NFS 서버)이 응답해야 풀립니다. 좀비는 이미 죽은 프로세스이며, 남은 것은 부모가 회수하지 않은 종료 상태뿐이라 죽일 대상이 없습니다. 부모를 처리해야 사라집니다.
ps -o pid,ppid,stat,wchan:24,args -p 1234
sudo cat /proc/1234/stack
퀴즈 6: 배포 직후 응답 지연이 생겼습니다. 애플리케이션 문제인지 네트워크 문제인지 한 번의 명령으로 좁히려면?
정답: curl의 타이밍 변수로 단계를 분해합니다
설명: 어느 구간에서 시간이 소모되는지 나누면 조사 범위가 즉시 줄어듭니다.
curl -sS -o /dev/null -w 'dns:%{time_namelookup} conn:%{time_connect} tls:%{time_appconnect} ttfb:%{time_starttransfer} total:%{time_total}\n' https://api.example.com/health
time_connect까지 정상인데 time_starttransfer만 크면 네트워크는 무고하고 서버가 응답을 만드는 데 오래 걸리는 것입니다. 반대로 time_namelookup이 크면 이름 해석 경로부터 봐야 합니다.
마치며
장애 대응을 잘하는 사람과 못하는 사람의 차이는 아는 명령어 개수가 아닙니다. "이 명령의 결과로 무엇을 배제할 수 있는가" 를 매번 의식하는지의 차이입니다.
vmstat을 치기 전에 "여기서 wa가 낮으면 디스크를 지운다"고 미리 정해 두면, 출력이 나온 순간 다음 행동이 결정됩니다. 그렇게 하지 않으면 같은 화면을 세 번 보고도 아무것도 좁히지 못합니다.
이 글의 명령 목록을 사내 위키에 그대로 옮기지 말고, 첫 60초 블록을 스크립트 하나로 만들어 모든 서버에 배포하세요. 장애가 났을 때 그 스크립트 하나를 실행하는 것이 아홉 개의 명령을 기억해 내는 것보다 훨씬 빠릅니다.
참고 자료
- vmstat(8) — man7.org (2026-08-15 확인)
- iostat(1) — man7.org (2026-08-15 확인)
- pidstat(1) — man7.org (2026-08-15 확인)
- ss(8) — man7.org (2026-08-15 확인)
- lsof(8) — man7.org (2026-08-15 확인)
- dmesg(1) — man7.org (2026-08-15 확인)
- journalctl(1) — man7.org (2026-08-15 확인)
이어서 읽기
- 다음 글: 리눅스 성능 도구 완전 가이드 — 출력의 숫자를 해석하는 법
- 리눅스 성능 엔지니어링 완전 가이드 — 프로파일링과 eBPF까지 들어가는 이론 편
- systemd 서비스 관리 완벽 가이드 — 서비스가 안 뜰 때의 진단
- DNS 해석 순서 디버깅 — 이름만 안 풀릴 때
- Linux 터미널 — 명령을 브라우저에서 바로 연습
- 리눅스 명령어 퀴즈 — 옵션 암기 상태 점검
- curl 빌더 — 타이밍 옵션이 붙은 curl 명령 만들기
The complete Linux troubleshooting command guide: from the first 60 seconds to root cause
- Introduction
- 1. The first 60 seconds — the standard triage sequence
- 2. What load average actually tells you
- 3. Who is using the CPU
- 4. Memory — which number in free do you believe
- 5. When the disk is full — capacity and inodes
- 6. I/O — finding the evidence of a slow disk
- 7. Network — ports, queues, and retransmits
- 8. Logs — narrow the time window first
- 9. Digging into a process that looks stuck
- 10. Always-on collection for incidents that will not reproduce
- Quiz: check your understanding
- Closing
- References
- Further reading
Introduction
The biggest waste of time during an incident is not caused by not knowing a command. It is caused by not knowing which command to run in which order. You bring up top, stare at it for 30 seconds, run df, come back to top — and five minutes are gone.
This post lays out, in order, the commands to run from the moment you get the call that says "the server is slow", "it stopped responding", or "something is off since the deploy". This blog already has a Linux performance engineering guide, but that one covers profiling and tuning methodology. This one is different. The subject here is the order in which you eliminate candidates one at a time while you still do not know what the problem is.
There is a single premise. Diagnosis is not about finding the culprit, it is about eliminating suspects. If it is not CPU, erase CPU. If it is not memory, erase memory. What remains is the answer.
Distribution notation is as follows. RHEL-family means RHEL/Rocky/AlmaLinux 8 and later; Debian-family means Debian 12 and Ubuntu 22.04 and later.
1. The first 60 seconds — the standard triage sequence
The "first 60 seconds" checklist published by the Netflix performance engineering team has effectively become the industry standard. The key is to fire ten commands at once and look at the whole picture first, and only then dig deep.
uptime
dmesg --level=err,warn --ctime | tail -20
vmstat 1 5
mpstat -P ALL 1 3
pidstat 1 3
iostat -xz 1 3
free -m
sar -n DEV 1 3
ss -s
Here is the suspect each command rules out.
| Command | Question it answers | What this eliminates |
|---|---|---|
uptime | Is load climbing right now, or falling? | An incident that is already over |
dmesg | Did the kernel kill or cut something off? | OOM kill, disk error, link down |
vmstat 1 | CPU wait, I/O wait, or swap? | Two of the three |
mpstat -P ALL | Is everything busy, or just one core? | A single-threaded bottleneck |
pidstat 1 | Which process is doing the work? | Every innocent process |
iostat -xz 1 | Is the disk falling behind? | Storage |
free -m | Is memory actually short? | Memory |
sar -n DEV | Is bandwidth saturated? | Network throughput |
ss -s | Are sockets leaking? | Connection leaks |
mpstat, pidstat, iostat, and sar all live in the sysstat package. It is frequently not part of a default install, so put it on the image when you build the server.
# RHEL family
sudo dnf install -y sysstat
# Debian family
sudo apt install -y sysstat
To read historical data with sar, the collector daemon has to be running. On Debian-family systems, collection only starts once you turn the enable value on in /etc/default/sysstat.
sudo systemctl enable --now sysstat
2. What load average actually tells you
The three numbers from uptime are the 1-minute, 5-minute, and 15-minute load averages. Two things here are easy to misread.
First, the Linux load average does not count CPU wait alone. It includes processes in uninterruptible sleep, the D state, as well as runnable (R) ones. D state is usually disk I/O or a wait on an NFS response. That is how you end up with a load of 40 while the CPU sits idle. In that case the culprit is storage, not CPU.
Second, the slope matters more than the absolute value. If the 1-minute figure is larger than the 15-minute figure, things are getting worse right now; if it is smaller, you are already recovering. In the latter case there is no reason to rush a restart.
uptime
14:22:31 up 41 days, 3:11, 2 users, load average: 12.44, 6.80, 3.15
This output means "load quadrupled over the last 15 minutes and is still climbing". It only means something relative to the core count, so check the core count first.
nproc
lscpu | grep -E '^CPU\(s\)|Thread|Core|Socket|Model name'
3. Who is using the CPU
The first line of vmstat 1 is the average since boot, so ignore it and start reading at the second line. This behaviour is stated in the man page.
vmstat 1 5
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
8 1 0 210488 91232 3120440 0 0 12 34 980 2210 71 14 9 6 0
Read it in this order.
r: the number of processes waiting to run. If it stays consistently above the core count, the CPU is saturated.b: the number of processes blocked waiting for I/O to complete. When this is large, suspect storage.si/so: swap in and out. Anything other than zero means memory pressure is already costing you performance.wa: the share of time spent waiting on I/O. High means disk; low but still slow means it is not the disk.st: time stolen by the hypervisor. On a cloud VM, a value that keeps exceeding 5 means the host is oversubscribed, and there is nothing you can solve from inside your own server.
Whether everything is busy or just one core is settled with mpstat.
mpstat -P ALL 1 3
If exactly one core sits at 100 percent, you have a single-threaded bottleneck. Scaling up will not fix it; you have to look at the code or the configuration.
Find the guilty process with pidstat. Unlike top, it keeps printing per-interval values rather than cumulative ones, which makes it good for capturing into a log.
pidstat -u 1 5
pidstat -t -p 1234 1 5
The -t option breaks the numbers down per thread. For thread-heavy processes such as Java or Go, you can see which thread is spinning. %wait is the share of time a task was ready to run but could not get a CPU, so when that is high the process is not slow, CPU contention is severe.
The classic ps combination is still useful too.
ps -eo pid,ppid,stat,pcpu,pmem,etime,rss,args --sort=-pcpu | head -15
In the stat column, D means uninterruptible sleep, Z is a zombie, and T is stopped.
4. Memory — which number in free do you believe
The column beginners misread most often in free -m is the free column. Linux uses spare memory as page cache. A small free is normal, and the column to look at is available.
free -m
total used free shared buff/cache available
Mem: 15884 10233 412 188 5238 5102
Swap: 4095 120 3975
available is the kernel estimate of "how much could be reclaimed and handed over if a new process asked for it right now". If that value is a healthy share of the total, you are not short on memory.
If you suspect memory pressure, start by checking what the kernel killed.
dmesg --ctime | grep -i -E 'out of memory|oom-kill|killed process'
journalctl -k --since '2 hours ago' | grep -i oom
dmesg --ctime (-T) converts timestamps to a human-readable clock, but the man page warns that timestamps can be inaccurate after a system suspend or resume. When you need precise times, use journalctl -k.
For a detailed breakdown, look at /proc/meminfo.
grep -E 'MemAvailable|Dirty|Writeback|Slab|SReclaimable|Committed_AS' /proc/meminfo
If Dirty is large and not shrinking, writes are failing to make it down to disk. If Slab is abnormally large, suspect a kernel object leak. For real per-process usage, RSS double-counts shared pages, so read PSS where you can.
sudo grep -H '^Pss:' /proc/1234/smaps_rollup
5. When the disk is full — capacity and inodes
Disk exhaustion comes in two kinds: block exhaustion and inode exhaustion. Looking only at df -h, answering "there is plenty free", and then discovering the inodes are actually gone is not a rare experience.
df -h
df -i
When IUse% in df -i reads 100 percent, you cannot create a new file no matter how much space is left. The cause is usually a directory where millions of small files pile up, such as session files or a mail queue.
When hunting for large directories, it is safer to restrict the recursion to a single filesystem.
sudo du -x -h --max-depth=1 /var | sort -h | tail -20
-x keeps it from crossing into other filesystems. Without it, you end up walking /proc and network mounts and it takes forever.
If df and du disagree badly, the reason is files that were deleted but are still open. Deleting a file does not return the space while a process that has it open is still alive.
sudo lsof +L1
sudo lsof -nP +L1 | awk '{print $1, $2, $7, $9}' | sort -k3 -n -r | head
+L1 lists open files whose link count is below 1, that is, files that have already been unlinked. The fix is to send the process a log-reopen signal or restart it. The mechanics are covered in this series in the file descriptors and inodes guide.
6. I/O — finding the evidence of a slow disk
iostat -xz 1 3
-x gives extended statistics and -z omits devices with no activity. The first report covers the period since boot, so drop it with -y or start reading at the second report.
The columns to read are these.
r_await/w_await: average response time in milliseconds, including the time a request waited in the queue. On NVMe, anything above single-digit milliseconds is odd; on spinning disks, 10 to 20 is still a normal range.aqu-sz: average queue length. Well above 1 means the device is not digesting requests.rareq-sz/wareq-sz: average request size in KiB. Tells small random I/O apart from large sequential I/O.%util: the share of time during which I/O was issued to this device.
%util needs care. The man page warns about it explicitly. On a device that serves requests serially, 100 percent means saturation, but on devices that process in parallel, such as a RAID array or a modern SSD, 100 percent is not the performance ceiling. In that case the number you judge by is await, not %util.
Which process is generating the I/O is answered by pidstat -d.
pidstat -d 1 5
kB_rd/s and kB_wr/s are per-process reads and writes, and iodelay shows block I/O delay in clock ticks. If iotop is installed, it is more comfortable to watch interactively.
sudo iotop -oPa
7. Network — ports, queues, and retransmits
netstat is a relic of the net-tools package, and the standard on current distributions is ss.
ss -tulpn
ss -tan state established | head
ss -s
-tTCP,-uUDP,-llistening only,-aeverything,-nno name resolution,-pshow the process using the socket.- Seeing process names with
-pusually requires root.
Recv-Q and Send-Q mean different things depending on the socket state. On a listening socket, Recv-Q is the length of the completed queue waiting to be accepted, and Send-Q is the backlog maximum. If Recv-Q on a listening socket stays full, the application is failing to keep up with accept. On an established connection they are, respectively, received data not yet read and sent data not yet acknowledged. Note, though, that the meaning of these two columns is not spelled out in the body of the ss manual published on man7.org; it rests on iproute2 implementation behaviour. Before you lean on it as evidence, it is safer to confirm it once against the man page of the iproute2 version you have installed.
Whether this is a path problem or an application problem gets settled by splitting it up by layer.
ip -brief addr
ip route get 10.0.3.14
ping -c 4 10.0.3.14
mtr -rwc 20 10.0.3.14
curl -sS -o /dev/null -w 'dns:%{time_namelookup} conn:%{time_connect} tls:%{time_appconnect} ttfb:%{time_starttransfer} total:%{time_total}\n' https://example.com
The curl timing variables show which stage is slow in a single line. A slow DNS stage is a name resolution problem, a slow time_connect is the path or a firewall, and a slow time_starttransfer on its own is the server application.
Check whether TCP retransmits are high.
nstat -az | grep -i -E 'retrans|drop|overflow'
ss -ti state established | grep -E 'retrans|rtt' | head
If ListenOverflows or ListenDrops is climbing, the backlog is overflowing.
8. Logs — narrow the time window first
Do not read logs from the beginning. Look at only the five minutes either side of the incident.
journalctl --since '2026-08-15 14:10' --until '2026-08-15 14:30' -p err
journalctl -u nginx.service -n 200 --no-pager
journalctl -u nginx.service -f
journalctl -k -b -1 -p warning
journalctl -g 'timeout|refused|denied' --since today
-p errshows only err priority and above. The priority names, in order, are emerg, alert, crit, err, warning, notice, info, debug.-b -1is the log from the previous boot. When a server reboots out of nowhere, the cause is in there.-gapplies a regular expression to the MESSAGE field.
Listing boots and checking the reason for a reboot goes like this.
journalctl --list-boots
last -x reboot shutdown | head
If last -x shows reboots with no clean shutdown record, suspect a kernel panic, a power problem, or a forced hypervisor restart. Designing the log pipeline itself is covered in the Linux logging operations guide.
9. Digging into a process that looks stuck
A process that stops responding is one of three things: burning CPU, waiting on something, or stuck on a lock.
Start with the wait point the kernel reports. These are reads with no side effects.
cat /proc/1234/status | grep -E 'State|Threads|voluntary'
sudo cat /proc/1234/stack
sudo cat /proc/1234/wchan; echo
ls -l /proc/1234/fd | head
A State of D (disk sleep) is uninterruptible sleep. A process in that state will not die even to SIGKILL. It only unblocks when the resource it is waiting on, usually storage or NFS, responds.
If you need to look at the syscall level, attach strace, but do so knowing full well that it makes the process substantially slower in production, and keep it short.
sudo strace -f -p 1234 -tt -T -e trace=network,file 2>&1 | head -50
sudo strace -c -f -p 1234
-c emits only a summary, so its overhead is comparatively low, and it shows at a glance which syscall is eating the time. If strace will not attach, the kernel ptrace restriction setting may be the reason.
cat /proc/sys/kernel/yama/ptrace_scope
A value of 1 or higher means even the same user cannot attach to an arbitrary process, and root is required.
Being stuck on a file lock is also common.
cat /proc/locks | head
sudo lsof /var/lib/myapp/data.db
10. Always-on collection for incidents that will not reproduce
The most common failure is "we did not capture the logs at the time". A momentary load spike is over before a human can log in, so collection has to already be running under normal conditions.
sar was built for exactly this. The default collection interval varies by distribution configuration and is usually 10 minutes.
sar -u -f /var/log/sa/sa15
sar -r -s 14:00:00 -e 15:00:00
sar -n DEV -s 14:00:00 -e 15:00:00
sar -q
-f points at a specific day file. The file path differs: /var/log/sa/ on RHEL-family, /var/log/sysstat/ on Debian-family.
A simple approach that snapshots only when a threshold is crossed works too. The following writes a process list to a file when load goes over a threshold.
#!/usr/bin/env bash
set -euo pipefail
THRESHOLD=20
LOAD=$(awk '{print int($1)}' /proc/loadavg)
if [ "$LOAD" -ge "$THRESHOLD" ]; then
TS=$(date +%Y%m%d-%H%M%S)
OUT="/var/log/spike-$TS.txt"
{
uptime
ps -eo pid,stat,pcpu,pmem,etime,args --sort=-pcpu | head -30
ss -s
vmstat 1 3
} > "$OUT"
fi
Hang that script off a one-minute cron job or a systemd timer, and the next load spike leaves evidence behind even with nobody there. For how to write timers, see systemd timers, fully explained.
Finally, take care that the commands you run while diagnosing do not make the incident worse. Running du -x from the root while forgetting the flag, or launching a full find / scan against a disk whose I/O is already saturated, makes the situation worse. Remember that when a disk is already 100 percent utilised, read work has to queue too.
Quiz: check your understanding
Quiz 1: Load average is 40 but the CPU usage in top adds up to 15 percent in total. What do you check first?
Answer: Processes in uninterruptible sleep (D state) and disk I/O
Why: The Linux load average includes processes in uninterruptible sleep (D) as well as runnable (R) ones. If the CPU is idle and only the load is high, the cause is usually a wait on storage or an NFS response. The order goes like this.
ps -eo pid,stat,wchan:20,args --sort=-pcpu | awk '$2 ~ /D/'
iostat -xz 1 3
vmstat 1 5
A large await in iostat together with a large b column in vmstat confirms a storage bottleneck.
Quiz 2: df -h shows 30 percent free, but the application fails when it tries to create a file. What are the two candidate causes?
Answer: Inode exhaustion, and the path actually belonging to a different (full) filesystem or hitting a quota
Why: Free blocks and free inodes are separate things. When millions of small files pile up, the inodes run out first even with capacity to spare.
df -i
df -h /var/lib/myapp
mount | grep myapp
What matters is passing the path to df directly and confirming which filesystem that path belongs to. /var being a separate mount is common.
Quiz 3: You deleted a log file but the usage reported by df did not change. Why, and how do you fix it?
Answer: The process that had the file open is still alive, so the inode has not been released. That process has to reopen the file before the space comes back
Why: unlink only removes the directory entry. As long as an open file descriptor remains, the data blocks stay.
sudo lsof -nP +L1 | head
sudo systemctl reload rsyslog
For logs, a reload that triggers a reopen is usually enough. Killing the process is a last resort. To avoid creating the situation in the first place, configure logrotate to send a reopen signal from a postrotate hook instead of using copytruncate.
Quiz 4: iostat reports %util at 100 percent. Can you conclude the disk is saturated?
Answer: No. On devices that process requests in parallel, 100 percent is not the limit
Why: The iostat man page states explicitly that on devices which process requests in parallel, such as RAID arrays and modern SSDs, this value does not reflect the performance limit. %util is nothing more than "the share of time during which at least one request was issued to this device". The judgement has to be made with r_await, w_await, and aqu-sz. If response time is at its usual level, 100 percent is not a problem.
Quiz 5: You sent SIGKILL to a process and it did not die. What could be going on, and how do you check?
Answer: The process is in D state (uninterruptible sleep), or it is already a zombie (Z)
Why: SIGKILL is enforced by the kernel, but in D state the signal delivery itself is deferred. It unblocks only when the resource the kernel is waiting on, storage or an NFS server, responds. A zombie is a process that has already died, and all that remains is an exit status the parent has not reaped, so there is nothing there to kill. It disappears once you deal with the parent.
ps -o pid,ppid,stat,wchan:24,args -p 1234
sudo cat /proc/1234/stack
Quiz 6: Response latency appeared right after a deploy. How do you narrow application versus network with a single command?
Answer: Break the request into stages with the curl timing variables
Why: Splitting out which segment consumes the time shrinks the scope of the investigation immediately.
curl -sS -o /dev/null -w 'dns:%{time_namelookup} conn:%{time_connect} tls:%{time_appconnect} ttfb:%{time_starttransfer} total:%{time_total}\n' https://api.example.com/health
If everything through time_connect is normal and only time_starttransfer is large, the network is innocent and the server is taking a long time to produce a response. Conversely, if time_namelookup is large, you start from the name resolution path.
Closing
The difference between people who are good at incident response and people who are not is not the number of commands they know. It is whether, every single time, they are conscious of what the result of this command lets them rule out.
If you decide in advance, before typing vmstat, that "if wa is low here I erase the disk", then the moment the output appears your next move is already determined. Without that, you can look at the same screen three times and narrow nothing at all.
Do not copy this list of commands into your internal wiki as-is. Turn the first-60-seconds block into a single script and roll it out to every server. Running that one script during an incident is far faster than recalling nine commands.
References
- vmstat(8) — man7.org (verified 2026-08-15)
- iostat(1) — man7.org (verified 2026-08-15)
- pidstat(1) — man7.org (verified 2026-08-15)
- ss(8) — man7.org (verified 2026-08-15)
- lsof(8) — man7.org (verified 2026-08-15)
- dmesg(1) — man7.org (verified 2026-08-15)
- journalctl(1) — man7.org (verified 2026-08-15)
Further reading
- Next: The complete Linux performance tools guide — how to read the numbers in the output
- The complete Linux performance engineering guide — the theory side, going as far as profiling and eBPF
- The complete systemd service management guide — diagnosing a service that will not start
- Debugging DNS resolution order — when it is only name resolution that fails
- Linux terminal — practise the commands straight from the browser
- Linux command quiz — check how well you remember the options
- curl builder — build curl commands with the timing options attached