디스크가 꽉 차서 생기는 장애는 유독 진단이 늦습니다. 증상이 “디스크가 없다”는 말로 나오지 않기 때문입니다. 워드프레스에서 이미지 업로드가 실패하고, MySQL이 알 수 없는 이유로 죽고, SSH로 로그인하자마자 프롬프트가 이상하게 뜹니다. 그래서 애먼 플러그인이나 PHP 설정을 뒤지다가 한참 뒤에야 진짜 원인을 만나게 됩니다.
디스크 용량 문제는 원인 자체는 단순한데, 찾는 순서를 모르면 오래 걸립니다. 루트 파티션 전체를 무작정 훑으면 시간만 잡아먹습니다. 위에서부터 범위를 좁히는 방식으로 접근하면 보통 2~3분 안에 범인이 나옵니다. 서버를 직접 운영한다면 한 번은 반드시 겪는 상황이라, 순서를 손에 익혀두는 편이 낫습니다.
한눈에 ·
df -h로 문제 파티션 특정 →du로 한 단계씩 파고들기 → 로그·캐시·백업 등 흔한 자리 정리 → 지웠는데 안 줄면lsof로 deleted 파일 확인 → logrotate·journald로 재발 방지
디스크가 꽉 찼다는 신호는 대개 엉뚱한 데서 먼저 옵니다
서버에 접속했는데 파일 하나 저장이 안 되고 이런 메시지가 뜨면, 그 순간 다른 걸 볼 필요가 없습니다.
-bash: /home/user/test.txt: No space left on device
[error] 12345#0: *1 open() "/var/log/nginx/access.log" failed (28: No space left on device)
MySQL 로그라면 Error writing file '/tmp/MYfd=xx' (Errcode: 28 - No space left on device) 같은 형태로 나옵니다. Errcode: 28이 곧 디스크 부족입니다. 로그인 직후 -bash: cannot create temp file 류가 뜨는 것도 같은 이유입니다.
이때 쓰는 도구는 두 개뿐입니다. 성격이 다릅니다.
| 명령 | 보는 대상 | 언제 쓰나 |
|---|---|---|
df | 파일시스템(파티션) 단위 요약 | 어느 마운트 지점이 찼는지 확인할 때 |
du | 디렉터리·파일별 실제 사용량 | 그 안에서 누가 먹는지 찾아 들어갈 때 |
df로 범위를 좁히고 du로 파고든다 — 이 순서만 지키면 됩니다. 반대로 하면 du /를 돌려놓고 몇 분씩 기다리게 됩니다. 참고로 서버에 자주 들어가야 한다면 SSH config 파일로 서버 별칭 등록하기, ssh 서버이름 한 줄로 접속하는 설정을 해두는 편이 급할 때 편합니다.
먼저 df -h로 어느 파티션이 문제인지 좁힙니다
에러를 확인했다면 다음은 범위 좁히기입니다. df는 파일시스템 사용량을 보여주고, -h는 사람이 읽기 쉬운 단위(G, M)로 바꿔줍니다.
df -h
df -h 출력 읽는 법
Filesystem Size Used Avail Use% Mounted on
udev 1.9G 0 1.9G 0% /dev
tmpfs 394M 1.1M 393M 1% /run
/dev/vda1 39G 37G 0 100% /
/dev/vdb1 98G 12G 82G 13% /home
tmpfs 2.0G 0 2.0G 0% /dev/shm
overlay 39G 37G 0 100% /var/lib/docker/overlay2/a1b2c3/merged
컬럼은 이렇게 읽습니다.
| 컬럼 | 의미 |
|---|---|
| Filesystem | 장치 이름. /dev/vda1 같은 실제 디스크 |
| Size | 전체 크기 |
| Used | 사용 중인 용량 |
| Avail | 남은 용량 |
| Use% | 사용률. 여기가 100%면 범인 |
| Mounted on | 이 파일시스템이 붙어 있는 경로 |
위 예에서는 /가 100%입니다. /home은 따로 잡혀 있고 13%밖에 안 씁니다. 즉 /home 아래를 아무리 뒤져도 소용없습니다. 찾아야 할 범위는 /home을 제외한 루트 파티션이라는 뜻입니다. 서버마다 /var나 /var/log가 별도 파티션인 경우도 있으니, 이 표를 먼저 봐야 헛수고를 안 합니다.
udev, tmpfs는 메모리 기반이라 대개 무시해도 됩니다. overlay는 도커 컨테이너의 파일시스템인데, 보통 루트 파티션 위에 얹혀 있어서 Use%가 /와 똑같이 나옵니다. 별개 디스크가 아니라 같은 디스크를 다르게 보여주는 것뿐입니다.

Use% 100%인데 Avail이 남아 보이는 이유(예약 블록)
Use% 100%인데 Avail에 몇백 MB가 남아 보이는 경우가 있습니다. ext4는 기본적으로 전체 용량의 5%를 root 전용으로 예약해 둡니다. 일반 사용자나 웹서버 프로세스는 그 공간을 못 씁니다. 그래서 root로는 파일이 써지는데 www-data로 도는 PHP는 계속 실패하는 상황이 생깁니다.
급하면 예약 비율을 줄일 수 있습니다. 다만 임시방편입니다.
sudo tune2fs -m 1 /dev/vda1
또 하나 같이 봐야 할 게 inode입니다. inode는 파일 하나당 하나씩 쓰이는 관리 정보 슬롯입니다. 용량이 남아도 이게 떨어지면 파일 생성이 안 되고 똑같이 No space left on device가 뜹니다.
df -i
Filesystem Inodes IUsed IFree IUse% Mounted on
/dev/vda1 2560000 2560000 0 100% /
IUse%가 100%라면 용량이 아니라 파일 개수 문제입니다. 이 경우 대응이 달라지는데, 뒤에서 다시 다룹니다.
du로 실제 용량을 먹는 디렉터리까지 파고들기
파티션을 특정했으니 이제 그 안에서 범인을 찾습니다. du는 디렉터리를 실제로 훑어서 크기를 더합니다. -s는 합계만, -h는 읽기 쉬운 단위입니다.
최상위부터 한 단계씩 내려가는 반복 패턴
먼저 최상위를 훑습니다.
sudo du -sh /* 2>/dev/null
2>/dev/null은 권한이 없거나 /proc 같은 가상 디렉터리에서 나오는 에러를 화면에서 걷어내는 부분입니다. sudo 없이 돌리면 du: cannot read directory '/root': Permission denied가 잔뜩 찍힙니다. 참고로 특정 디렉터리만 계속 접근이 막힌다면 퍼미션 자체가 잘못됐을 수도 있습니다. 이 부분은 chmod 755와 644의 실제 차이, 왜 폴더는 755여야 하나를 같이 보면 정리가 됩니다.
결과에서 /var가 크게 나왔다고 해봅시다. 그러면 한 단계 더 들어갑니다.
sudo du -h --max-depth=1 /var | sort -h
--max-depth=1은 딱 한 단계 아래만 보여주라는 뜻입니다. 안 그러면 하위 디렉터리를 전부 출력해서 화면이 넘어갑니다. 이 명령의 경로만 바꿔가며 반복하는 게 du 탐색의 전부입니다.

sort -h로 큰 순서 정렬하기
sort -h는 1.2G, 340M 같은 사람이 읽는 단위를 크기순으로 제대로 정렬해 줍니다. 그냥 sort를 쓰면 문자열로 비교해서 엉뚱한 순서가 나옵니다.
디렉터리 안에서 큰 것 20개만 뽑고 싶을 때는 이 조합이 편합니다.
cd /var/log
sudo du -sh * | sort -h | tail -20
그리고 -x 옵션을 기억해 두면 좋습니다. 현재 파일시스템 밖으로 넘어가지 않게 막아줍니다.
sudo du -shx /* 2>/dev/null
/home이 다른 디스크로 마운트돼 있는데 -x 없이 /부터 훑으면, 문제와 상관없는 /home 98GB까지 다 계산하느라 시간이 날아갑니다. df -h에서 마운트가 여러 개로 보였다면 -x를 붙이세요.
큰 파일 하나만 콕 집어 찾기
디렉터리가 아니라 파일 하나가 범인인 경우도 많습니다. 코어 덤프, 잘못 만든 백업 tar, 폭주한 로그 파일 같은 것들입니다.
sudo find / -xdev -type f -size +500M -exec ls -lh {} \; 2>/dev/null
-size +500M은 500MB보다 큰 파일, -xdev는 다른 마운트로 안 넘어가게 하는 옵션입니다. 출력이 없으면 임계값을 +100M으로 낮춰서 다시 돌려보면 됩니다.
흔한 범인들: 로그, 캐시, 백업, journald
이렇게 좁혀 들어가면 걸리는 자리가 대체로 정해져 있습니다. 경험상 아래 네 곳에서 거의 다 나옵니다.
/var/log가 1순위입니다. logrotate가 안 걸린 error.log나 access.log가 수 GB로 자라 있는 경우입니다.
sudo du -sh /var/log/* | sort -h | tail -10
systemd 로그(journald)도 조용히 몸집을 불립니다. 얼마나 쓰는지 먼저 확인합니다.
journalctl --disk-usage
Archived and active journals take up 3.8G in the file system.
3.8GB면 충분히 문제가 됩니다. 200MB만 남기고 정리합니다.
sudo journalctl --vacuum-size=200M
나머지 단골들은 이렇습니다.
| 위치 | 정리 방법 | 안전한가 |
|---|---|---|
/tmp | 오래된 파일 삭제 | 실행 중 프로세스가 쓰는 파일이 있을 수 있어 주의 |
/var/cache/apt | sudo apt clean | 안전 |
/var/cache/yum, dnf | sudo dnf clean all | 안전 |
/var/lib/docker | docker system prune -a | 안 쓰는 이미지까지 지우므로 확인 후 |
wp-content/uploads | 중복 이미지·오래된 미디어 정리 | 사이트에서 참조 중일 수 있음 |
| 백업 플러그인 산출물 | wp-content/updraft, ai1wm-backups 등 | 대체로 안전, 외부 보관 확인 후 |
워드프레스 서버라면 백업 플러그인이 남긴 압축 파일이 의외로 크게 잡힙니다. UpdraftPlus나 All-in-One WP Migration이 서버 로컬에만 백업을 쌓아두는 설정이면 몇십 GB까지 갑니다.
로그 파일은 rm으로 지우지 말고 비우는 편이 낫습니다. 실행 중인 프로세스가 그 파일을 열어둔 상태라면 지워도 용량이 안 돌아옵니다.
sudo truncate -s 0 /var/log/nginx/access.log
지웠는데 용량이 안 늘어날 때 (df와 du 값이 다름)
방금 그 이야기가 실제로 제일 많이 막히는 지점입니다. 큰 로그를 rm으로 지웠는데 df -h는 여전히 100%입니다. du -sh /로 더해보면 20GB밖에 안 나오는데 df는 37GB를 썼다고 합니다.
| df | du | |
|---|---|---|
| 세는 기준 | 파일시스템이 기록한 블록 사용량 | 실제 존재하는 파일들의 합 |
| 삭제됐지만 열려 있는 파일 | 포함됨 | 제외됨 |
| 마운트에 가려진 파일 | 포함됨 | 안 보임 |
lsof로 deleted 파일 잡는 법
리눅스는 파일을 지워도 그 파일을 열고 있는 프로세스가 있으면 실제 공간을 반환하지 않습니다. 디렉터리에서 이름만 사라진 상태입니다. 확인은 lsof로 합니다.
sudo lsof | grep deleted
nginx 1423 www-data 5w REG 253,1 4831838208 262148 /var/log/nginx/access.log (deleted)
+L1 옵션을 쓰면 링크 수가 0인, 즉 삭제된 파일만 골라서 보여줍니다.
sudo lsof +L1
해결은 두 가지입니다. 가장 확실한 건 해당 서비스를 재시작해서 파일 핸들을 놓게 하는 것입니다.
sudo systemctl restart nginx
재시작이 곤란하다면 파일 디스크립터를 직접 비웁니다. 위 출력에서 PID가 1423, FD가 5w였으니 숫자만 떼서 이렇게 씁니다.
sudo sh -c ': > /proc/1423/fd/5'
이후 df -h를 다시 쳐서 Use%가 내려갔는지 확인합니다. 안 내려갔다면 아직 다른 프로세스가 붙잡고 있는 것이니 lsof +L1을 한 번 더 봅니다.
또 다른 원인으로, 마운트하기 전에 그 경로에 파일을 쌓아둔 경우가 있습니다. 예를 들어 /mnt/data에 파일을 넣어둔 뒤 그 위에 디스크를 마운트하면, 원래 파일은 가려져서 du에 안 잡히지만 공간은 계속 차지합니다.
inode 부족일 때 작은 파일 대량 찾기
df -i에서 IUse% 100%가 나왔다면 큰 파일이 아니라 작은 파일 수백만 개가 문제입니다. 세션 파일, 메일 큐, 캐시 조각이 흔한 원인입니다. 어느 디렉터리에 파일이 몰려 있는지는 이렇게 셉니다.
sudo find /var -xdev -type f | cut -d/ -f1-4 | sort | uniq -c | sort -n | tail -20
PHP 세션이라면 /var/lib/php/sessions, 메일이면 /var/spool/postfix/maildrop이 자주 걸립니다. 파일이 너무 많아 rm *가 Argument list too long으로 실패하면 이렇게 우회합니다.
sudo find /var/lib/php/sessions -type f -mtime +7 -delete
같은 일을 또 겪지 않으려면
찾아서 지우는 것까지 끝냈다면 남는 건 재발 방지입니다. 다음에 할 일을 체크리스트로 정리합니다.
- [ ]
ls /etc/logrotate.d/로 문제였던 서비스의 rotate 설정이 있는지 확인합니다. 없으면 만들어 둡니다. - [ ]
/etc/systemd/journald.conf에서SystemMaxUse=200M을 지정하고sudo systemctl restart systemd-journald를 실행합니다. - [ ] 워드프레스 백업 플러그인의 보관 개수를 2~3개로 줄이고, 가능하면 외부 스토리지로 보내도록 바꿉니다.
- [ ]
sudo apt install ncdu(또는dnf install ncdu)로 ncdu를 설치해 둡니다.sudo ncdu -x /로 실행하면 화살표 키로 디렉터리를 들어가며 크기를 볼 수 있어서,du를 반복해서 치는 것보다 훨씬 빠릅니다.d키로 그 자리에서 삭제도 됩니다. - [ ] 임계값 알림을 걸어 둡니다. cron에 아래 한 줄짜리 스크립트를 등록하면 90% 넘을 때 메일이 옵니다.
# /etc/cron.hourly/disk-check
#!/bin/bash
USE=$(df -h / | awk 'NR==2 {gsub("%","",$5); print $5}')
[ "$USE" -ge 90 ] && echo "Disk usage on / is ${USE}%" | mail -s "[ALERT] disk $(hostname)" admin@example.com
sudo chmod +x /etc/cron.hourly/disk-check
용량 사고는 항상 새벽에 터지므로, 알림 한 줄이 진단 명령 열 개보다 낫습니다. 위 스크립트는 /만 보니, df -h에서 별도 파티션이 있었다면 경로를 추가해 두세요.
서버 관리 명령을 자주 원격에서 쳐야 한다면 접속이 끊기는 문제부터 잡아두는 게 편합니다. SSH 접속이 자꾸 끊길 때, timeout 설정으로 잡는 법에 그 설정을 정리해 뒀습니다.

[…] -1k는 1KB 미만, -size 0 또는 -empty는 빈 파일입니다. 디스크가 꽉 찼을 때는 디스크 용량 부족, df와 du로 범인 찾아내는 순서로 큰 디렉터리를 먼저 좁힌 다음 find -size로 파고드는 게 […]