Deleting a 2 GB log with rm freed nothing on our test server. Used space read 5.0G before the delete and 5.0G after it, because a tail process still had the file open and the kernel keeps the blocks until the last reader lets go. That’s how most people first try to empty a log file in Linux, and it fails without an error. To truncate log files safely, you empty them in place, so the inode survives and the writer keeps writing.
This guide covers truncate -s 0, the sudo redirect trap, getting space back from a log you already deleted, emptying many logs at once, and handing the job to logrotate or journald. Every command below was run on Debian 13 with GNU coreutils 9.7 in October 2026, with a live writer appending to the log during the truncate tests. The few exceptions are marked untested where they appear.
Use truncate -s 0 to empty a log file
truncate -s 0 is the method to reach for first, because it sets the file’s size to zero without removing or recreating it. The process writing the log keeps its open file descriptor, the disk blocks are released at once, and nobody has to restart anything. You need write permission on the file, which for anything under /var/log means sudo. The commands in this guide are plain coreutils, bash and systemd, so they carry over to Ubuntu, the RHEL family and Fedora, although Debian is the only distribution we ran them on.
sudo truncate -s 0 /var/log/demoapp/app.log
To see what that does to a file that is in use, we ran it on a 2.1 GB log while a loop appended a heartbeat line every second. The size and disk checks on each side of the truncate show what you should expect on your own server.
ls -lh /var/log/demoapp/app.log; df -h / | tail -1; sudo truncate -s 0 /var/log/demoapp/app.log; ls -l /var/log/demoapp/app.log; df -h / | tail -1; sleep 3; ls -l /var/log/demoapp/app.log; cat /var/log/demoapp/app.log
Watch the used column on the root filesystem, then the size of the file three seconds later. The first two lines are the state before the truncate, and the next two come straight after it.
-rw-r--r-- 1 root root 2.1G Oct 20 10:10 /var/log/demoapp/app.log
/dev/sda1 40G 5.1G 33G 14% /
-rw-r--r-- 1 root root 0 Oct 20 10:10 /var/log/demoapp/app.log
/dev/sda1 40G 3.1G 35G 8% /
-rw-r--r-- 1 root root 57 Oct 20 10:10 /var/log/demoapp/app.log
10:10:19 heartbeat
10:10:20 heartbeat
10:10:21 heartbeat
Used space dropped from 5.1G to 3.1G the moment the file hit zero, and three seconds later it held 57 bytes of fresh heartbeat lines at the start of the file. That is the healthy result: the writer didn’t notice, and new lines begin at offset 0. If you see the file jump straight back to its old size instead, the application isn’t opening its log in append mode, and the section on how the application writes its log tells you what to do about it.
Test the other ways to empty a file
Most of the shell shortcuts you’ll find for emptying a file do the same thing as truncate. They open the existing file with truncation and write nothing, so the inode survives. We filled seven copies of a 100,000-line file in /tmp and emptied each one a different way, as a normal user in bash. Create the test files first, so you can repeat the comparison yourself.
cd /tmp; for m in truncate colon redirect echo echon dd cp; do seq 1 100000 > t-$m.log; done
Each file starts at 588895 bytes. Empty them with the seven methods from the same shell, since the file names are relative to /tmp.
truncate -s 0 t-truncate.log
: > t-colon.log
> t-redirect.log
echo "" > t-echo.log
echo -n > t-echon.log
dd if=/dev/null of=t-dd.log status=none
cp /dev/null t-cp.log
We recorded size and inode with stat -c "%n %s %i" before and after. Every inode stayed the same, so none of these methods replaced the file, and six of the seven came out at zero bytes. The odd one is the echo "" form that a lot of older guides still recommend, because echo always adds a newline unless you pass -n.
| Method | Before | After | Inode before / after |
|---|---|---|---|
truncate -s 0 file | 588895 | 0 | 390 / 390 |
: > file | 588895 | 0 | 391 / 391 |
> file | 588895 | 0 | 392 / 392 |
echo "" > file | 588895 | 1 | 393 / 393 |
echo -n > file | 588895 | 0 | 394 / 394 |
dd if=/dev/null of=file status=none | 588895 | 0 | 395 / 395 |
cp /dev/null file | 588895 | 0 | 396 / 396 |
You can confirm the leftover byte with od, which prints every character in the file. It’s a quick way to tell an empty file from one that only looks empty in cat.
od -c /tmp/t-echo.log
It is one newline and nothing else. Without -n, echo always ends its output with one.
0000000 \n
0000001
One byte won’t hurt disk space, but it does leave a blank first line that log parsers and wc -l will count. We’d pick truncate -s 0 for scripts and runbooks because it reads clearly, works the same under sudo, and has a -c flag that matters once globs are involved. The redirect forms are fine when you clear a file you own from an interactive shell.
Run truncate with sudo on root-owned logs
Logs under /var/log belong to root, and that is where the redirect shortcuts break. sudo only elevates the command after it, while your own unprivileged shell opens the > redirect before sudo starts. The result is a permission error and an untouched log.
bash: /var/log/demoapp/app.log: Permission denied
Both of these look like they should work, and neither does. The first is a bare redirect and the second the colon form, each with sudo in front.
sudo > /var/log/demoapp/app.log; echo "rc=$?"; sudo : > /var/log/demoapp/app.log
The shell refuses to open the file and returns 1. The log keeps every byte. Check the size afterwards, because a quiet terminal does not tell you it worked.
bash: line 1: /var/log/demoapp/app.log: Permission denied
rc=1
bash: line 1: /var/log/demoapp/app.log: Permission denied
The fix is to make the privileged process the one that opens the file. Any of these three emptied the root-owned log in our test, and you should default to the first because it needs no extra shell.
sudo truncate -s 0 /var/log/demoapp/app.log
sudo sh -c ": > /var/log/demoapp/app.log"
: | sudo tee /var/log/demoapp/app.log
The sh -c form moves the redirect inside a root shell. The tee form works because tee itself runs as root and opens the file for writing, and it returned 0 without printing anything, since there was nothing to copy.
truncate: cannot open for writing: Operation not permitted
If root itself can’t empty a log, check for the append-only attribute. Some audit and security setups set chattr +a on their logs so the content can’t be rewritten, and that blocks truncation for every user, root included. lsattr shows it as an a in the flags.
lsattr /var/log/demoapp/audit.log
On our file the flags looked like this. Both truncate and a root shell redirect were refused with the same reason.
-----a--------e------- /var/log/demoapp/audit.log
truncate: cannot open '/var/log/demoapp/audit.log' for writing: Operation not permitted
sh: 1: cannot create /var/log/demoapp/audit.log: Operation not permitted
Somebody set that attribute on purpose, so find out why before you remove it. If emptying the file is approved, drop the flag and truncate, which worked in our test. The third line restores the flag so the protection stays in place.
sudo chattr -a /var/log/demoapp/audit.log
sudo truncate -s 0 /var/log/demoapp/audit.log
sudo chattr +a /var/log/demoapp/audit.log
The same attribute is behind most “Operation not permitted” errors on files root should be able to touch. The guide on fixing Operation not permitted when deleting a file covers the immutable flag as well, which you clear the same way.
Find a deleted log that still holds space
If you already removed a big log with rm and the disk is still full, the file is not really gone. rm only removes the directory entry. The kernel keeps the data until the last process holding the file closes it, so the space comes back when that process exits or when you truncate the file through /proc. We reproduced it with a 2 GB copy of the log open in tail -f.
df -h / | tail -1; sudo rm /var/log/demoapp/worker.log; df -h / | tail -1
Used space did not move after the delete. The 2 GB is still allocated but no longer reachable by name, so ls and du will not show it to you. Only df still counts it.
/dev/sda1 40G 5.0G 33G 14% /
/dev/sda1 40G 5.0G 33G 14% /
Restarting the process that holds the file frees the space, but you can get it back without a restart. It takes four steps, in this order:
- Install
lsofif it’s missing - Find the process holding the deleted file
- Read its descriptor number under
/proc - Truncate that descriptor link
The Debian 13 cloud image does not ship lsof, and our first attempt ended with sudo: lsof: command not found. Install it first.
sudo apt-get install -y -qq lsof
On RHEL, Rocky Linux or Fedora the equivalent is sudo dnf install lsof, which we did not run for this guide.
lsof +L1 lists open files with a link count below one, which means deleted. On its own it is noisy, because every daemon still running an old binary after a package upgrade shows up as txt or mem lines. You only care about logs, so filter for the log directory.
sudo lsof -nP +L1 | grep -E "^COMMAND|/var/log"
The tail process, PID 10462, holds descriptor 3 on the deleted file, and the size column still reads 2147483933 bytes.
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NLINK NODE NAME
tail 10462 root 3r REG 8,1 2147483933 0 133891 /var/log/demoapp/worker.log (deleted)
Every open descriptor is a link under /proc/<PID>/fd, and writing to that link reaches the deleted file. Take the PID straight from the PID column of the lsof output, because a name search can match more than one process. Then list its descriptors to confirm the number before you truncate anything. Replace 10462 with the PID from your own output.
P=10462
sudo ls -l /proc/"$P"/fd
Descriptor 3 points at worker.log (deleted), matching the 3r in the FD column. Truncate the link, with a disk check on each side so you can see the result.
df -h / | tail -1; sudo truncate -s 0 /proc/"$P"/fd/3; df -h / | tail -1
Our capture below found the same PID with pgrep -x tail, which is safe only when exactly one process has that name. It shows the whole sequence on the lab server, ending with used space falling from 3.1G to 1.1G.

The 2 GB came back while tail kept running, and a second lsof +L1 showed the same deleted entry at size 0. The entry only disappears once the process closes the file or exits, which costs nothing now that it’s empty. If you need to find which log is eating the disk in the first place, ncdu shows where space really goes. A gap between what it counts and what df reports is the usual sign of a deleted file that’s still open.
truncate: cannot open ‘/proc/10462’ for writing: Is a directory
We hit this on an early attempt, which used pgrep -f "tail -f /var/log/demoapp/worker.log" to find the PID. The -f flag matches the full command line. The shell running our own command line contained the same text, so the variable held two PIDs, and the path split on the newline into /proc/10462 and 10641/fd/.
truncate: cannot open '/proc/10462' for writing: Is a directory
truncate: cannot open '10641/fd/' for writing: No such file or directory
Paste the PID from the lsof output, or match the process name exactly with pgrep -x when only one such process exists, and the path resolves to a single descriptor. Quote "$P" and check it with echo before you truncate through it, because a wrong path here fails loudly but a wrong PID could point at a different file.
Truncate log files in bulk
Emptying a whole directory of logs is where the quiet failures pile up, because two shell rules decide which files the command actually sees. The first rule is that ** doesn’t recurse in bash unless globstar is on, and it’s off by default. Our test tree held four logs:
/var/log/demoapp/a.log/var/log/demoapp/app.log/var/log/demoapp/nested/b.log/var/log/demoapp/nested/deep/c.log
Check what the recursive pattern expands to before you hand it to truncate. echo shows the expansion without touching any file.
shopt globstar; echo /var/log/demoapp/**/*.log
Only one of the four files matched, because with globstar off, ** behaves like a single * and matches exactly one directory level.
globstar off
/var/log/demoapp/nested/b.log
After shopt -s globstar, the same pattern matched all four. The second rule bites harder. A glob expands in your own shell, as your user, before sudo starts. In a directory only root can read, nothing matches, so bash passes the literal string *.log through and truncate creates a file with that name.
sudo truncate -s 0 /var/log/private-app/*.log; echo rc=$?; sudo ls -l /var/log/private-app/
The exit code says success, yet secret.log still holds its 3893 bytes, and a new empty file called *.log sits next to it.
rc=0
total 4
-rw-r--r-- 1 root root 0 Oct 20 10:12 *.log
-rw-r--r-- 1 root root 3893 Oct 20 10:12 secret.log
Let root expand the glob by quoting the whole command into sh -c. Run that way, secret.log dropped to 0 bytes.
sudo sh -c "truncate -s 0 /var/log/private-app/*.log"
Add -c (--no-create) to any truncate that takes a glob as well. After we removed the stray file and repeated the broken command as sudo truncate -c -s 0 /var/log/private-app/*.log, it still returned 0 but created nothing, so a pattern that matches nothing can’t litter the directory. It does not fix the expansion, though, which is why the sh -c wrapper comes first.
For anything recursive or selective, find is more predictable than a glob, because it walks the tree as root and lets you filter by size. This empties every .log over 100 KB at any depth and leaves small ones alone.
sudo find /var/log/demoapp -type f -name "*.log" -size +100k -exec truncate -s 0 {} +
All three large files went from 288894 bytes to 0, from the top-level a.log down to nested/deep/c.log, and the 380-byte app.log was skipped.
-rw-r--r-- 1 root root 0 Oct 20 10:12 /var/log/demoapp/a.log
-rw-r--r-- 1 root root 380 Oct 20 10:12 /var/log/demoapp/app.log
-rw-r--r-- 1 root root 0 Oct 20 10:12 /var/log/demoapp/nested/b.log
-rw-r--r-- 1 root root 0 Oct 20 10:12 /var/log/demoapp/nested/deep/c.log
Run the same find with -exec ls -l {} + first, so you see the list before anything is emptied. Keep the path narrow to the application’s own directory. A broad -name "*log" across all of /var/log also empties the auth and system logs that other services write, and those are the ones you’d want during an incident. The find command guide has more filters, such as -mtime for logs that haven’t changed in a week.
Keep the last lines of a log
Sometimes you want the space back but still need the most recent entries for an open incident. Copy the tail to a temporary file and write it back over the log with cat, which keeps the same inode, so a writer holding the file isn’t cut off. A mv of the temp file over the log would swap in a new inode and leave the application writing to the old, invisible one.
sudo sh -c 'T=$(mktemp) && tail -n 1000 /var/log/demoapp/a.log > "$T" && cat "$T" > /var/log/demoapp/a.log && rm -f "$T"'
Our lab ran the same commands with a fixed /tmp/a.keep path. The mktemp version avoids clobbering a file someone else left there, and running it inside the root shell means root owns the temp file. With stat -c "%s bytes inode %i" before and after, the file shrank from 588895 to 6001 bytes, and the inode stayed 133913.
588895 bytes inode 133913
6001 bytes inode 133913
99001
99002
The first two lines of the result are 99001 and 99002, which are exactly the last thousand lines of the original 100,000. Lines a writer appends between the tail and the cat are lost, so on a busy log keep that window short and run the whole thing as one command, as shown.
Check how the application writes its log
Truncation only works cleanly when the writer opened the log in append mode. A process using O_APPEND always writes at the current end of the file, so after a truncate it starts again at byte 0. A process that opened the file for plain writing keeps its own offset and carries on from there, so the kernel fills the gap with a hole. We tested both with a writer that holds the file open for its whole life. With exec 3>> (append) and 10 MB of base64 data a second, the log was 82M before the truncate, and two seconds after it the two size commands agreed.
ls -lh /var/log/demoapp/held.log; sudo du -h /var/log/demoapp/held.log; sudo head -c 16 /var/log/demoapp/held.log | od -c | head -1
Both report 28M, and the file still starts with base64 text, so the writer simply carried on from byte 0. You get the space back and a log you can still read.
-rw-r--r-- 1 root root 28M Oct 20 10:30 /var/log/demoapp/held.log
28M /var/log/demoapp/held.log
0000000 O a 0 T x G Y x D S f G Y X h /
Then we repeated it with a writer that opened the file once with exec 3> instead, writing the same data. Two seconds after truncate -s 0, the sizes disagreed.
ls -lh /var/log/demoapp/batch.log; sudo du -h /var/log/demoapp/batch.log
ls reports the apparent size, and du reports the blocks actually allocated. On a normal log the two agree.
-rw-r--r-- 1 root root 136M Oct 20 10:11 /var/log/demoapp/batch.log
28M /var/log/demoapp/batch.log
The file is sparse: 136M long, 28M on disk, and the start of it is nothing but NUL bytes. Reading the first 64 bytes shows it.
sudo head -c 64 /var/log/demoapp/batch.log | od -c | head -3
Every byte is \0. The * line means od collapsed the repeated rows of zeros.
0000000 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0
*
0000100
Disk space is still recovered, so this is not an emergency, but anything that reads the file from the top meets a block of binary zeros before the first real line. You can check how a process opened its log before you truncate by reading the flags in /proc/<PID>/fdinfo.
P=$(pgrep -f "exec 3>/var/log/demoapp/batch.log" | head -1); sudo cat /proc/$P/fdinfo/3 | head -3
The flags field is octal. 0100001 is write-only with large-file support, and there’s no 02000 append bit in it, which is why the writer kept its 169979724-byte offset.
pos: 169979724
flags: 0100001
mnt_id: 29
For a writer like that, truncating is the wrong fix. Your options, from best to last resort:
- open the log with append (
>>in shell scripts, not>) - use the application’s reopen signal, or restart it after rotation
truncateorcopytruncate, accepting the sparse file
Our heartbeat loop never showed the problem either, because it reopened the file with >> for every line. If you write the application yourself, that one character is the whole fix.
Shrink the systemd journal instead of truncating it
Never truncate the .journal files under /var/log/journal. They are structured binary files with their own headers, and journald has a supported way to shrink them. Start by checking how much space the journal takes.
sudo journalctl --disk-usage
After we pushed six batches of random test data through logger, it reported this. That figure is the starting point for the vacuum commands below.
Archived and active journals take up 69.3M in the file system.
Vacuuming only deletes archived journal files and never the active ones. Rotate first, so the current data moves into an archive, then vacuum down to a size.
sudo journalctl --rotate; sudo journalctl --vacuum-size=50M; sudo journalctl --disk-usage
journald names each file it deletes and the total it freed, which here was 35.5M. The 0B lines are journal directories with nothing archived to remove, and the 41M line is the final --disk-usage, printed out of order because the commands mix stdout and stderr.
Vacuuming done, freed 0B of archived journals from /run/log/journal.
Vacuuming done, freed 0B of archived journals from /var/log/journal.
Deleted archived journal /var/log/journal/270c874bbd574897a173170275b4931c/system@c497d599bf1b48bba7375a768588d75a-0000000000000001-00065e42aca306ae.journal (2.5M).
Archived and active journals take up 41M in the file system.
Deleted archived journal /var/log/journal/270c874bbd574897a173170275b4931c/user-1000@c497d599bf1b48bba7375a768588d75a-000000000000068e-00065e42d17b7a03.journal (32.9M).
Vacuuming done, freed 35.5M of archived journals from /var/log/journal/270c874bbd574897a173170275b4931c.
sudo journalctl --vacuum-time=1s goes further and drops every archived file, which freed another 25M and left the journal at 16M of active files. Both are one-off cleanups, and the journal grows back to its default cap afterwards. To set your own limit, put it in a drop-in so a package upgrade can’t overwrite it. The stock /etc/systemd/journald.conf on Debian 13 has SystemMaxUse and SystemKeepFree commented out, and the drop-in directory doesn’t exist yet.
sudo mkdir -p /etc/systemd/journald.conf.d
Create the drop-in file. The name is up to you, as long as it ends in .conf.
sudo vim /etc/systemd/journald.conf.d/size.conf
These two lines cap the journal at 200M and tell journald to always leave 1G of the filesystem free, whichever limit is hit first.
[Journal]
SystemMaxUse=200M
SystemKeepFree=1G
Restart journald and read its startup line, which states the limit it applied. Restarting it does not cost you messages already written to disk.
sudo systemctl restart systemd-journald
sudo journalctl -b -u systemd-journald --no-pager | tail -2
“max 200M” confirms the drop-in was read. The 184M free figure is the room left under the new cap.
Oct 20 10:13:28 deb01 systemd-journald[11718]: Journal started
Oct 20 10:13:28 deb01 systemd-journald[11718]: System Journal (/var/log/journal/270c874bbd574897a173170275b4931c) is 16M, max 200M, 184M free.
The journald.conf reference lists the other limits, such as MaxRetentionSec for time-based retention. For reading what is left in the journal, the journalctl filtering guide covers units, priorities and time ranges.
Set up logrotate with copytruncate
Manual truncation is for the day a disk fills up. To stop it happening again, logrotate’s copytruncate mode does the same in-place truncate on a schedule, after first copying the content to a rotated file. It suits an application that can’t be told to reopen its log. The Debian 13 cloud image we used didn’t ship logrotate at all, so there was no /etc/logrotate.conf and nothing rotating application logs until we installed it.
sudo apt-get install -y logrotate
On RHEL, Rocky Linux or Fedora the command is sudo dnf install logrotate. We did not run it, and we did not check whether those images already include the package.
The package ships a systemd timer that runs logrotate, and logrotate only looks at your logs when that timer fires. Check that it’s scheduled.
systemctl list-timers logrotate.timer --no-pager | head -2
The NEXT column shows the first run, with nothing in LAST yet, because the timer had never fired on this fresh install.
NEXT LEFT LAST PASSED UNIT ACTIVATES
Wed 2026-10-21 00:46:45 UTC 14h - - logrotate.timer logrotate.service
The schedule matters more than most people expect, so read it from the unit itself.
systemctl cat logrotate.timer | grep -E "OnCalendar|RandomizedDelaySec|AccuracySec|Persistent"
On Debian 13 the timer fires once a day with up to an hour of random delay, and Persistent=true catches up a run missed while the machine was off.
OnCalendar=daily
RandomizedDelaySec=1h
Persistent=true
Give the application its own file under /etc/logrotate.d, so a package update to the main config never touches it.
sudo vim /etc/logrotate.d/demoapp
Add the rule for the log. The path in front of the braces is the file it manages.
/var/log/demoapp/app.log {
rotate 7
size 100M
compress
delaycompress
missingok
notifempty
copytruncate
}
Read from the top, the rule does five things:
- keeps seven rotated copies
- rotates at the next run if the log is over 100M
- compresses old copies, one rotation late
- skips a missing or empty log
- copies the log, then truncates it in place
Our first lab file also carried a daily line above size, which we removed here. The logrotate man page says size and the time options are mutually exclusive and the last one listed wins. In that file size came last, so it was already in charge and daily did nothing. Be clear on what “size” means here: logrotate checks the size only when it runs. With the stock daily timer, a log that passes 100M at 09:00 waits for the next run, and maxsize with daily gives no earlier rotation either. Do a dry run first, which prints the plan without touching any file.
sudo logrotate -d /etc/logrotate.d/demoapp 2>&1 | grep -E "rotating pattern|considering|needs|does not need"
Against a small 10-line log, the trimmed rule parsed cleanly and correctly decided there was nothing to do yet. If you see a syntax error here instead, fix it now, because the timer will hit the same error at its next run.
rotating pattern: /var/log/demoapp/app.log 104857600 bytes empty log files are not rotated, (7 rotations), old logs are removed
considering log /var/log/demoapp/app.log
log does not need rotating (log size is below the 'size' threshold)
Then run it for real, which we did against a 151M test log. You should see the same lines with your own path in them.
sudo logrotate -v /etc/logrotate.d/demoapp 2>&1 | grep -E "rotating|copying|truncating|needs|considering"
The first line shows the 104857600-byte threshold in force, which is the 100M from the config. The last two lines are the copy and the in-place truncate.
rotating pattern: /var/log/demoapp/app.log 104857600 bytes empty log files are not rotated, (7 rotations), old logs are removed
considering log /var/log/demoapp/app.log
log needs rotating
rotating log /var/log/demoapp/app.log, log->rotateCount is 7
copying /var/log/demoapp/app.log to /var/log/demoapp/app.log.1
truncating /var/log/demoapp/app.log
Afterwards app.log was 0 bytes, app.log.1 held the 151M, and the heartbeat writer was already filling the live file again. The man page warns that copytruncate can lose the lines written between the copy and the truncate. Prefer create with a postrotate reload when your application supports it, and the rsyslog rotation guide shows that pattern for syslog files.
If a log can fill the disk in less than a day, make the timer fire more often instead of shrinking the size threshold. A drop-in keeps the change out of the packaged unit.
sudo mkdir -p /etc/systemd/system/logrotate.timer.d
Create the override file. You can give it any name that ends in .conf.
sudo vim /etc/systemd/system/logrotate.timer.d/hourly.conf
The empty OnCalendar= clears the packaged daily schedule. Without it, systemd adds the hourly entry next to the daily one instead of replacing it.
[Timer]
OnCalendar=
OnCalendar=hourly
Reload systemd, restart the timer, and check the new schedule. You need the daemon-reload, because systemd does not pick up a new drop-in on its own.
sudo systemctl daemon-reload; sudo systemctl restart logrotate.timer; systemctl list-timers logrotate.timer --no-pager
NEXT is now about an hour away instead of the next day. It reads 1h 2min rather than under an hour because the packaged RandomizedDelaySec=1h still applies to every run.
NEXT LEFT LAST PASSED UNIT ACTIVATES
Tue 2026-10-20 11:33:55 UTC 1h 2min Tue 2026-10-20 10:13:53 UTC - logrotate.timer logrotate.service
1 timers listed.
Pass --all to see loaded but inactive timers, too.
Rules with size or maxsize are now checked every hour, while plain daily rules still rotate once a day because logrotate tracks when each log last rotated.
logrotate: command not found
After the install, running logrotate --version as a normal user still printed bash: line 1: logrotate: command not found. The binary is at /usr/sbin/logrotate, and a regular user’s PATH on our Debian 13 server was /usr/local/bin:/usr/bin:/bin:/usr/games, which skips /usr/sbin. Run it with sudo, which searches its own path including /usr/sbin, and it’s found. We saw this on Debian 13 and did not check other distributions. It is one of the small gaps of a minimal cloud image, along with the missing lsof, that the Debian 13 post-install checklist helps you close before a full disk forces the issue.
Pick the method for your situation
The right tool depends on whether the file still exists, who writes to it, and whether you need the space once or for good. This is the order we’d work through on a server that is running out of disk.
| Situation | Method | Watch out for |
|---|---|---|
| One large log, app still running | sudo truncate -s 0 file | Sparse file if the app doesn’t append |
Log is root-owned and you used > | sudo sh -c ": > file" or sudo truncate | The redirect runs as your user |
Already ran rm, disk still full | lsof +L1, then truncate /proc/PID/fd/N | pgrep -f can return two PIDs |
| Many logs, nested directories | find ... -size +100k -exec truncate -s 0 {} + | Never *log across all of /var/log |
| Need the latest entries | tail -n 1000 to a temp file, cat it back | Lines written in between are lost |
| Truncate refused even as root | chattr -a, truncate, chattr +a | Find out why the flag was set |
| Journal is large | journalctl --vacuum-size and SystemMaxUse | Never truncate .journal files |
| It keeps happening | logrotate with copytruncate or create | size and daily: the last one listed wins, and size is only checked when the timer runs |
Most of the time the first row is all you need. When df and du disagree after a cleanup, go to the third row before you reach for a reboot. Once the immediate fire is out, put a logrotate rule or a journald cap in place, so the same log can’t fill your disk again.