肥大化したログファイルを消すときはrmではなくtruncate -s 0を使う。ファイルを残したまま中身だけを空にできるため、ディスク容量がすぐに解放され、アプリケーションのログ出力も止まらない。

肥大化したログでディスクが埋まった

ログの出力量が想定より多く、ディスクを圧迫することがある。

$ df -h /var/log
Filesystem      Size  Used Avail Use% Mounted on
/dev/loop0      974M  801M  107M  89% /var/log

$ du -sh /var/log/myapp/app.log
801M	/var/log/myapp/app.log

とりあえず容量を空けたいので、rmでログファイルを削除する。

$ rm /var/log/myapp/app.log

しかし、空き容量は変わらない。

$ df -h /var/log
Filesystem      Size  Used Avail Use% Mounted on
/dev/loop0      974M  801M  107M  89% /var/log

rmで容量が解放されない理由

rmが削除するのはディレクトリエントリ、つまりファイル名とinodeの結び付きである。ファイルの実体は、名前が消えたうえにどのプロセスからも開かれていない状態になって初めて解放される。ログを出力し続けているプロセスがファイルを開いたままなら、名前が消えても実体は残り、ディスクを占有し続ける。

削除済みのまま開かれているファイルはlsof +L1で確認できる。

$ sudo lsof +L1 /var/log
COMMAND PID USER   FD   TYPE DEVICE  SIZE/OFF NLINK NODE NAME
bash    240 root    1w   REG    7,0 838862194     0   13 /var/log/myapp/app.log (deleted)

NLINKが0でNAMEに(deleted)の付いた項目が該当する。容量を解放するにはプロセスの再起動が必要になる。

さらに、ログの出力先が消えるため、新しいログはどこにも残らない。プロセスは削除済みのファイルへ書き込み続ける。lstailには現れず、ディスクだけが減っていく状態になる。

rmのあとにtouchでファイルを作り直しても解決しない。プロセスが握っているのは削除済みの実体であり、同名の新しいファイルではないため、書き込み先は元のままである。加えて、作り直したファイルの所有者とパーミッションは作成者のものになる。

$ ls -l /var/log/myapp/app.log
-rw-r----- 1 appuser appuser 6 Aug 15 00:46 /var/log/myapp/app.log

$ sudo rm /var/log/myapp/app.log
$ sudo touch /var/log/myapp/app.log
$ ls -l /var/log/myapp/app.log
-rw-r--r-- 1 root root 0 Aug 15 00:46 /var/log/myapp/app.log

所有者がrootに変わったため、appuserとして動くアプリケーションは次回の起動でログを書き込めなくなる。パーミッションも640から644に緩んでおり、他のユーザーからログの中身が見える状態になっている。ログの権限を戻すには、元の所有者とパーミッションを調べてchownchmodで設定し直す必要がある。

truncate -s 0でログをクリアする

truncateはファイルのサイズを指定した値に変更するコマンドで、-s 0を指定すると中身が空になる。ディレクトリエントリとinodeはそのまま残るため、ファイルを開いているプロセスは開き直す必要がない。

$ sudo truncate -s 0 /var/log/myapp/app.log

空き容量はその場で解放される。

$ df -h /var/log
Filesystem      Size  Used Avail Use% Mounted on
/dev/loop0      974M   32K  907M   1% /var/log

ログの出力も継続する。プロセスを再起動する必要はない。

$ tail -n 3 /var/log/myapp/app.log
2026-08-15 00:47:54 INFO request handled
2026-08-15 00:47:54 INFO request handled
2026-08-15 00:47:54 INFO request handled

所有者とパーミッションも変わらない。

$ ls -l /var/log/myapp/app.log
-rw-r----- 1 appuser appuser 0 Aug 15 00:46 /var/log/myapp/app.log

rmと比べたメリットをまとめると次のようになる。

項目rmtruncate -s 0
空き容量の解放プロセスの終了まで待つ即座に解放される
ログ出力の継続削除済みファイルに書き込まれ続けるそのまま継続する
所有者・パーミッション作り直すと変わる維持される
プロセスの再起動必要不要

複数のログをまとめてクリアする場合はファイルを並べて指定する。

$ sudo truncate -s 0 /var/log/myapp/*.log

シェルのリダイレクトとの違い

: > file> fileもファイルのサイズを0にする。ファイルをO_TRUNC付きで開き直す動作であり、inodeが残る点はtruncate -s 0と同じである。

$ : > /var/log/myapp/app.log

ただしリダイレクトを処理するのはシェルであり、sudoでコマンドを起動する前に出力先のファイルを開く。書き込み権限のないファイルへsudoを付けても失敗する。

$ sudo : > /var/log/myapp/app.log
bash: /var/log/myapp/app.log: Permission denied

sudo sh -c ': > /var/log/myapp/app.log'のようにシェルごと昇格させる書き方もあるが、クォートが煩雑になる。truncateはコマンド自体がファイルを開くため、sudoをそのまま前置できる。

$ sudo truncate -s 0 /var/log/myapp/app.log

find-execxargsと組み合わせられる点も、コマンドであるtruncateの利点である。

$ sudo find /var/log -name '*.log' -size +100M -exec truncate -s 0 {} \;

find -sizeの指定方法は【find -size】指定サイズ以上(以下)のファイルを検索する を参照。

追記モードで開いていない場合の注意点

ログファイルをO_APPEND(追記モード)で開いていないプロセスがある場合、truncateの直後にファイルサイズが元へ戻ったように見える。プロセスが保持する書き込みオフセットはサイズ変更の影響を受けず、次の書き込みが切り詰める前と同じ位置で発生する。

追記モードなしで開いたファイルをtruncate -s 0した場合の挙動を確認する。

$ exec 3> /tmp/noappend.log
$ for i in $(seq 1 100000); do echo "2026-08-15 00:46:12 INFO request handled" >&3; done
$ ls -l /tmp/noappend.log
-rw-r--r-- 1 root root 4100000 Aug 15 00:49 /tmp/noappend.log

$ truncate -s 0 /tmp/noappend.log
$ ls -l /tmp/noappend.log
-rw-r--r-- 1 root root 0 Aug 15 00:49 /tmp/noappend.log

$ echo "2026-08-15 00:46:30 INFO request handled" >&3
$ ls -l /tmp/noappend.log
-rw-r--r-- 1 root root 4100041 Aug 15 00:49 /tmp/noappend.log

1行書いただけでサイズが約4MBに戻っている。ファイルの先頭には、何も書かれていない領域を埋めるNUL文字が並ぶ。

$ head -c 30 /tmp/noappend.log | cat -v
^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@

ただし、この領域はスパースファイルの穴でありディスクブロックを消費しない。duで見ると実際の使用量は解放されている。

$ du -h /tmp/noappend.log
4.0K	/tmp/noappend.log

そのため容量を空ける目的は達成できる。しかしlsの表示が実際の使用量とかけ離れ、catや転送のときにNUL文字が現れるため扱いにくい。

追記モードで開いている場合はオフセットが常にファイル末尾へ更新されるため、この現象は起きない。

$ exec 4>> /tmp/append.log
$ for i in $(seq 1 100000); do echo "2026-08-15 00:46:12 INFO request handled" >&4; done
$ truncate -s 0 /tmp/append.log
$ echo "2026-08-15 00:46:30 INFO request handled" >&4
$ ls -l /tmp/append.log
-rw-r--r-- 1 root root 41 Aug 15 00:49 /tmp/append.log

一般的なログ出力ライブラリやシェルの>>は追記モードでファイルを開くため、通常は問題にならない。lsのサイズが戻る現象を見つけたら、追記モードなしで開くプロセスを疑う。開き方は/proc/[PID]/fdinfo/[FD]のflagsで確認できる。8進数で表示され、O_APPEND(02000)のビットが立っていれば追記モードである。

$ cat /proc/$$/fdinfo/3
pos:	4100041
flags:	0400001
mnt_id:	279
ino:	6557743

$ cat /proc/$$/fdinfo/4
pos:	41
flags:	0402001
mnt_id:	279
ino:	6557730

0402001は02000のビットが立っているため追記モードである。0400001は立っていないため追記モードではない。posの値がそのまま次の書き込み位置になる。

truncateのその他の使い方

-sには0以外のサイズも指定できる。単位としてK、M、Gなどを使える。指定したサイズより大きいファイルは切り詰められ、小さいファイルはNUL文字で埋めて拡張される。

$ truncate -s 100M big.log
$ ls -l big.log
-rw-r--r-- 1 root root 104857600 Aug 15 00:44 big.log

拡張した領域はスパースファイルの穴になるため、実際のディスク使用量は増えない。

$ du -h big.log
0	big.log

+-を付けると現在のサイズからの相対指定になる。

$ truncate -s -50M big.log
$ ls -l big.log
-rw-r--r-- 1 root root 52428800 Aug 15 00:44 big.log

$ truncate -s +10M big.log
$ ls -l big.log
-rw-r--r-- 1 root root 62914560 Aug 15 00:44 big.log

truncateは存在しないファイルを指定すると空のファイルを作成する。作成せずに既存ファイルだけを対象にしたい場合は-c(--no-create)を付ける。ログのクリアをスクリプト化する場合、パスの打ち間違いで無関係な空ファイルが増えるのを防げる。

$ truncate -s 0 -c nonexistent.log
$ ls nonexistent.log
ls: cannot access 'nonexistent.log': No such file or directory

-r(--reference)で基準となるファイルを指定すると、そのファイルと同じサイズに切り詰める。

$ ls -l /tmp/ref.txt
-rw-r--r-- 1 root root 5 Aug 15 00:46 /tmp/ref.txt

$ truncate -r /tmp/ref.txt a.log
$ ls -l a.log
-rw-r--r-- 1 root root 5 Aug 15 00:46 a.log

logrotateとの関係

継続的にログのサイズを抑えるならlogrotateを設定する。truncate -s 0は、logrotateの設定が間に合っていない場合や、ディスクが逼迫して即座に容量を空けたい場合の応急処置と位置付ける。

なお、logrotateのcopytruncateオプションは、ログファイルを別名にコピーしてから元のファイルを切り詰める。プロセスがファイルを開いたままでもローテートできる仕組みであり、実質的にtruncateと同じ動作をする。コピーと切り詰めの間に書き込まれたログが失われる点はmanページにも明記されており、追記モードで開いていないプロセスでの注意点も同じく当てはまる。

systemdのジャーナルは/var/log/journal配下のバイナリファイルであり、truncateの対象にしない。サイズを減らすにはjournalctl --vacuum-size=100Mのような専用のコマンドを使う。journalctlの使い方はjournalctlでサービスのログを見る を参照。

補足

ログを消す前に中身を残したい場合は、圧縮してからtruncateする。

$ sudo gzip -c /var/log/myapp/app.log > /tmp/app.log.$(date +%Y%m%d).gz
$ sudo truncate -s 0 /var/log/myapp/app.log

コピーと切り詰めの間に書き込まれたログは失われる。厳密さが必要な場面ではlogrotateやアプリケーション側のローテート機能を使う。

どのディレクトリが容量を占めているかの調べ方は【du】ディスクを圧迫しているディレクトリを探す を参照。空き容量が十分あるのに書き込めない場合は【Linux】df -iでinodeの枯渇を調査する を参照。

macOSにも/usr/bin/truncateがあり、-s 0-c-rはGNU版と同様に使える。

オプションの詳細はtruncate(1) - Linux manual page を参照。