Linux: "No space left on device"인데 df에는 여유 공간이 보임 (inode)
안녕하세요, BlueByte입니다. 디스크에는 수 GB가 남아 있고 df -h도 여유가 있다고 하는데, 정작 모든 쓰기가 No space left on device로 실패합니다. 깨진 것은 없습니다 — 파일시스템의 inode가 바닥났을 뿐이고, df -h는 그것을 보여주지 않습니다. 증상은 touch·cp·로그 쓰기·애플리케이션 오류에서 ENOSPC / No space left on device가 나는데 df -h는 공간이 넉넉하다고 표시하는 것입니다. 오늘은 여유 공간과 "공간 없음"이 어떻게 동시에 참일 수 있는지, inode 문제임을 확인하는 법, 무엇이 inode를 먹는지 찾기, 원인별 해결, 그리고 예방까지 하나씩 짚어보겠습니다.
여유 공간이 있는데도 "No space left on device"가 나는 이유
ext4 파일시스템의 모든 파일은 두 가지를 필요로 합니다. 내용을 담을 데이터 블록, 그리고 메타데이터(소유자·권한·타임스탬프·블록 위치)를 담을 inode 하나입니다. df -h는 블록을 잽니다. 그런데 inode의 개수는 파일시스템을 만들 때 정해지며, mke2fs 매뉴얼에 따르면 "it is not possible to change this ratio on a file system after it is created"(생성 후에는 이 비율을 바꿀 수 없음)입니다. 작은 파일로 가득한 디렉터리는 파일마다 inode 하나씩을 소모하면서 블록은 거의 건드리지 않습니다. inode가 다 떨어지면 커널은 ENOSPC — 디스크가 꽉 찼을 때와 똑같은 오류 — 를 반환하는데, df -h는 여전히 수 GB가 남았다고 표시합니다. 두 가지가 이 증상을 흉내 내기도 합니다. 확인한 것과 다른 마운트에 쓰는 경우, 그리고 삭제됐지만 아직 열려 있는 파일입니다 — 다만 후자는 df -h에서 꽉 찬 것으로 보이므로 반대 신호입니다.
먼저 df에 블록이 아니라 inode를 물어보기
여기서는 df -h만 믿지 마세요 — -i로 inode 사용량을 물어봅니다. coreutils 매뉴얼은 이를 "list inode usage information instead of block usage"(블록 대신 inode 사용 정보를 표시)로 정의합니다:
df -h /
df -i /Filesystem Size Used Avail Use% Mounted on
/dev/sda1 193G 135G 58G 71% /
Filesystem Inodes IUsed IFree IUse% Mounted on
/dev/sda1 26083328 3502488 22580840 14% /이건 건강한 파일시스템입니다 — inode 14% 사용, 두 열 모두 여유가 있습니다. 고갈된 경우에는 IUse% 열이 100%인데 df -h의 Use%는 여전히 여유를 보입니다. IUse%가 100이면 원인을 찾은 것입니다. 아니라면 애초에 맞는 파일시스템을 보고 있는지 확인하세요 — 실패하는 정확한 경로에 대고 df -i를 실행합니다(df -i /var/lib/app). 쓰기가 별도 마운트에 떨어질 수 있기 때문입니다.
작은 파일로 넘치는 디렉터리 찾기
inode가 사라진 건 무언가가 작은 파일을 산더미처럼 만들었기 때문입니다. 상위 디렉터리별로 파일 수를 세어 그 디렉터리를 찾습니다:
sudo find / -xdev -type f -printf '%h\n' | sort | uniq -c | sort -rn | head4211872 /var/spool/postfix/maildrop
38214 /var/log/journal/9f0a...
9021 /home/app/.cache-xdev는 find를 한 파일시스템 안에 붙들어 /proc나 다른 마운트로 새지 않게 합니다. 맨 윗줄 — 메일 스풀 하나에 400만 개가 넘는 파일 — 이 범인입니다. 그 디렉터리 하나가 inode 테이블을 말려버렸습니다.
inode를 먹은 파일을 지워서 해결하기
디렉터리를 알아냈으면 지워도 되는 것을 지웁니다. 항목이 수백만 개인 디렉터리는 rm *으로 감당이 안 됩니다 — 셸이 그만큼의 인자를 펼치지 못해 Argument list too long으로 실패합니다 — 그러니 제자리에서 삭제합니다:
sudo find /var/spool/postfix/maildrop -type f -delete기대 결과: inode가 해제되면서 df -i가 곧바로 내려갑니다. 파일이 정당한 것이고 정말로 inode가 더 필요하다면, 실시간 리사이즈는 없습니다 — mke2fs -i <bytes-per-inode>로 더 촘촘한 inode 비율로, 또는 -N으로 고정 개수를 지정해 파일시스템을 다시 만든 뒤 백업에서 복원합니다. 이는 ext2/3/4에 해당합니다. XFS와 Btrfs는 inode를 동적으로 할당해 같은 방식의 고정 상한에 걸리지 않으므로, 이 문제를 볼 일이 드뭅니다.
실제 사례: 폭주한 세션 디렉터리
PHP 앱이 failed to open stream: No space left on device를 던지기 시작하는데 df -h /는 60 GB가 남았다고 합니다. df -i /를 실행하니 IUse%가 100%입니다. 파일 수 스캔이 /var/lib/php/sessions를 가리키고, 만료되지 않은 세션 파일이 600만 개 들어 있습니다. find /var/lib/php/sessions -type f -mtime +2 | head로 오래된 것임을 확인한 뒤 sudo find /var/lib/php/sessions -type f -mtime +2 -delete로 지웁니다. df -i가 20%로 떨어지고 앱은 다시 씁니다 — 블록은 처음부터 문제가 아니었습니다.
inode가 돌아오고 쓰기가 되는지 확인하기
시작할 때 봤던 그 열을 다시 보고, 쓰기가 되는지 증명합니다:
df -i /
touch /var/lib/app/probe && echo "write ok" && rm /var/lib/app/probe/dev/sda1 26083328 5231044 20852284 21% /
write okIUse%가 100보다 넉넉히 낮고 touch가 성공하면 파일시스템이 다시 파일을 만들 수 있는 상태입니다. touch가 여전히 실패하면 엉뚱한 디렉터리를 지운 것이니 파일 수 스캔을 다시 돌려보세요.
inode 테이블이 차오르지 않게 예방하기
작은 파일이 쌓이기 전에 만료시키세요: 메일 스풀·세션 저장소·캐시 디렉터리에 systemd-tmpfiles나 cron find -mtime +N -delete로 보존 정책을 겁니다. df -h뿐 아니라 df -i를 모니터링하세요 — IUse%에 대한 알림이 쓰기가 멈추기 며칠 전에 이 문제를 잡아줍니다. 그리고 작은 파일을 많이 담을 걸 아는 파일시스템을 만들 때는, 나중에 바꿀 수 없으니 mke2fs 시점에 inode 비율을 그에 맞게 잡아두세요.
디스크가 꽉 찬 경우, 삭제-열림 파일과의 차이
df -h가 Use% 100%를 보이면 블록이 꽉 찬 것입니다 — 평범한 디스크 풀이고, 컨테이너 호스트라면 대개 이미지·로그 레이어라 docker system prune으로 정리하지 이 문제가 아닙니다. df -h와 df -i 모두 멀쩡한데 공간이 사라진 것 같다면, 어떤 프로세스가 삭제된 파일을 열어둔 채라 그 파일이 닫히기 전까지 블록이 회수되지 않는 것입니다 — sudo lsof +L1로 찾습니다. 세 경우 중 df -h는 여유를 보이고 df -i는 꽉 찬 것으로 나오는 건 오직 inode 고갈뿐입니다.
관련 질문
df -h는 여유 공간이 넉넉한데 왜 모든 쓰기가 실패하나요?
df -h는 데이터 블록만 세고 inode는 세지 않기 때문입니다. 파일마다 inode 하나가 필요한데, 고정된 inode 풀이 고갈되면 블록이 남아 있어도 커널이 똑같은 No space left on device(ENOSPC)를 반환합니다. df -i를 실행해 IUse% 열을 확인하세요.
리포맷 없이 inode를 늘릴 수 있나요?
아니요. ext2/3/4에서 inode 개수는 mkfs 시점에 정해지고 실시간으로 리사이즈할 수 없습니다. mke2fs -i(bytes-per-inode)나 -N(고정 개수)으로 파일시스템을 다시 만들고 백업에서 복원해야 합니다. XFS와 Btrfs는 inode를 동적으로 할당해 이 고정 상한이 없습니다.
가득 찬 디렉터리에서 rm *이 'Argument list too long'으로 실패합니다.
디렉터리 항목이 너무 많아 셸이 인자로 펼치지 못하는 것입니다. find <dir> -type f -delete로 제자리 삭제하면 거대한 인자 목록을 만들지 않습니다.
df -i도 멀쩡한데 여전히 No space left on device가 납니다.
두 가지 가능성이 있습니다. 확인한 것과 다른 마운트에 쓰고 있거나(실패하는 정확한 경로에 df -i 실행), 어떤 프로세스가 삭제된 파일을 열어둔 채라 블록이 해제되지 않는 경우입니다 — sudo lsof +L1로 찾아 그 프로세스를 재시작하세요.
XFS에서도 이 문제가 생기나요?
드뭅니다. XFS는 파일을 만들 때 inode를 동적으로 할당해 ext4처럼 고정 inode 상한에 걸리지 않습니다. 진짜로 디스크가 꽉 찬 경우(inode가 아니라 블록)는 어떤 파일시스템에서도 ENOSPC를 반환합니다.
참고 자료
Haneul Seo
Infrastructure engineer · 10+ years running Linux fleets
같은 카테고리 다른 글
systemd: Start request repeated too quickly
systemd는 StartLimitIntervalSec(기본 10초) 안에 StartLimitBurst(기본 5회)보다 많이 시작된 유닛의 시작을 거부하며, Restart=도 이 제한에 포함됩니다. 기본 RestartSec 100ms로는 크래시하는 서비스가 1초도 안 되어 다섯 번을 소진합니다. 저널에서 진짜 크래시를 찾아 고치고, reset-failed를 실행한 뒤, RestartSec로 재시작에 여유를 주세요.
SSH: Received disconnect ... Too many authentication failures
agent가 서버가 허용하는 것보다 많은 키를 내밀고 있습니다. sshd가 들여다보는 공개키 하나하나가 MaxAuthTries 시도를 한 번씩 소모하고 — 기본 6회, 하드닝된 호스트에서는 3회인 경우가 많습니다 — 정작 맞는 키는 차례가 오기 전에 연결이 끊깁니다. IdentitiesOnly=yes와 명시적 IdentityFile로 키 하나를 고정하면 시도 횟수가 1로 떨어집니다.
Active Directory: 복제가 error 1722(The RPC server is unavailable)로 실패
RPC는 하위 계층이 연결에 실패했을 때 1722(0x6ba, RPC_S_SERVER_UNAVAILABLE)를 보고합니다. 따라서 진짜 원인은 RPC 자신이 아니라 DNS, 차단된 포트, 두 도메인 컨트롤러 중 한쪽의 호스트 설정입니다. repadmin이 어느 파트너가 실패하는지 알려주고, dcdiag /test:dns가 이름 해석을 배제하며, Test-NetConnection과 동적 포트 범위가 방화벽 문제를 가릅니다. 가장 흔한 실수는 TCP 135만 허용하고 49152–65535는 막아 둔 규칙입니다.
Windows Server RDS: The remote session was disconnected because there are no Remote Desktop License Servers available to provide a license
120일 RD Licensing 유예 기간이 끝났는데 세션 호스트가 쓸 수 있는 라이선스 서버가 없어 세션을 거부하는 상황입니다. GetGracePeriodDays 의 DaysLeft 가 0 이고 SpecifiedLSList 가 비어 있으면 몇 초 만에 확인됩니다. 해결은 활성화된 라이선스 서버와, 호스트 버전을 감당할 만큼 새 CAL 입니다. 2019 CAL 은 2022 세션 호스트를 서비스하지 못합니다. 여기에 배포 또는 Licensing 정책 설정과 두 서버 사이의 RPC 포트 개방이 따라옵니다.
Windows 11: NAS 공유 폴더를 열 때 "조직의 보안 정책이 인증되지 않은 게스트 액세스를 차단" (0x80070035)
Windows 10 Enterprise/Education/Pro for Workstations, Windows 11 Pro, Windows Server 2019 이후의 SMB 클라이언트는 기본적으로 게스트 로그온을 거부하고, Windows 11 24H2 Enterprise/Pro/Education 은 게스트 세션이 할 수 없는 SMB 서명까지 요구합니다. 그래서 게스트 접근만 제공하는 NAS 공유는 'block unauthenticated guest access' 대화상자, Error code 0x80070035, 또는 System error 3227320323 으로 실패하고, SmbClient/Security 로그에 Event ID 31017 'Rejected an insecure guest logon' 이 남습니다. Microsoft 가 권하는 해결은 NAS 에 실제 계정을 만들고 펌웨어에서 서명을 지원하게 하는 것입니다. Set-SmbClientConfiguration -EnableInsecureGuestLogons $true(24H2 는 -RequireSecuritySignature $false 까지)는 비상구이며, 그 클라이언트의 서명과 암호화를 포기하는 대가가 있습니다.
Ubuntu/Debian: E: Could not get lock /var/lib/dpkg/lock-frontend — 누가 쥐고 있고 어떻게 기다리는지
다른 패키지 관리자, 대개 부팅 때 persistent systemd 타이머로 발화한 Ubuntu 의 unattended-upgrades 가 여러분의 apt-get 이 도는 동안 dpkg 프런트엔드 락을 쥐고 있는 것입니다. Ubuntu 는 apt 바이너리에만 binary::apt::DPkg::Lock::Timeout "120" 을 실어 두어서 apt-get 은 즉시 포기하고 apt 는 기다립니다. 메시지의 PID 를 읽고, 실행이 끝나게 두거나 apt-get 에 -o DPkg::Lock::Timeout=<초> 를 주고, dpkg --configure -a 는 실제로 중단된 실행 뒤에만 쓰고, 락 파일은 절대 지우지 마세요. 보유자가 종료하면 커널이 푸는 fcntl 락입니다.