Safely Removing Old Log Files with find and xargs
Log files are the lifeblood of debugging, but they’re also the biggest culprits when a disk starts to feel cramped. If you’re running a home server, a small NAS, or just a personal laptop, you’ll often need a quick way to prune logs older than a set age without accidentally wiping something vital. The classic find … | xargs combo is a battle‑tested, script‑friendly solution that works on almost every Linux box.
Below is a step‑by‑step walk‑through that shows how to:
- Locate the files you want to delete.
- Double‑check the list before you hit delete.
- Delete with safeguards against accidental mass removal.
- Automate the routine with a cron job.
- Deal with edge cases like long names or spaces.
The examples use the GNU coreutils version of find and xargs—the ones that ship on most distros. If you’re on BSD or another variant, the syntax may shift a bit, but the overall pattern stays the same.
Why find + xargs is a Good Fit
- Speed:
findscans once;xargsbatches the delete calls, cutting down onrminvocations. - Safety: Pipe the output through
lessorcatto review before you delete. - Flexibility: Combine size, age, name, and more in one line.
- Portability: Both tools are POSIX‑standard and present on virtually all Unix‑like systems.
1. Locating Old Log Files
Let’s say you want to delete every .log file in /var/log that’s older than 30 days.
find /var/log -type f -name '*.log' -mtime +30
-type fkeeps the search to regular files.-name '*.log'matches the pattern.-mtime +30grabs files modified more than 30 days ago.
Run it alone first—it just lists the candidates. That’s your sanity check.
Handling Spaces and Special Characters
If a filename contains spaces, newlines, or other oddities, the default find output can break the pipeline. Use -print0 to emit a null‑terminated list, and pair it with xargs -0:
find /var/log -type f -name '*.log' -mtime +30 -print0 | xargs -0 ls -l
The -print0/-0 dance guarantees every filename is parsed correctly, no matter what characters it holds.
2. Verifying Before Deletion
A common rookie mistake is to pipe straight into rm. Instead, first pipe into less or cat:
find /var/log -type f -name '*.log' -mtime +30 -print0 | xargs -0 -n 1 -I {} echo {}
The echo step shows each path. If everything looks right, replace echo with rm -i to prompt for each deletion:
find /var/log -type f -name '*.log' -mtime +30 -print0 | xargs -0 -i rm -i {}
rm -i forces an interactive prompt. If you’re comfortable with the list, skip the prompt with rm -f.
3. The Production‑Ready Delete Command
Once you’ve verified the list, run the actual delete. The safest form uses xargs -r (run only if there’s input) and -0 for null separation:
find /var/log -type f -name '*.log' -mtime +30 -print0 | xargs -r -0 rm -f
-
Why
-r?
Iffindreturns nothing,xargswould otherwise invokermwith no arguments, which could delete the current directory or do nothing.-rprevents that. -
Why
-f?
-fforces deletion without prompting. You’ve already checked the list, so it’s safe.
4. Automating with Cron
Most homelab owners want this to run automatically. Create a script, e.g., /usr/local/bin/clean_logs.sh:
#!/usr/bin/env bash
# clean_logs.sh
# Delete /var/log/*.log files older than 30 days
LOG_DIR="/var/log"
AGE_DAYS=30
find "$LOG_DIR" -type f -name '*.log' -mtime +$AGE_DAYS -print0 | \
xargs -r -0 rm -f
Make it executable:
chmod +x /usr/local/bin/clean_logs.sh
Add a cron job that runs once a week:
0 2 * * 0 /usr/local/bin/clean_logs.sh
This schedules the script to run at 02:00 on Sundays. Adjust the timing to match your disk‑usage patterns.
5. Advanced Filters
5.1 Size‑Based Deletion
Sometimes you want to target logs that are both old and large. Combine -size with -mtime:
find /var/log -type f -name '*.log' -mtime +30 -size +10M -print0 | \
xargs -r -0 rm -f
This removes files older than 30 days that exceed 10 MiB.
5.2 Excluding Certain Subdirectories
If you need to keep logs from a particular service, use -not -path:
find /var/log -type f -name '*.log' -mtime +30 \
-not -path '/var/log/nginx/*' -print0 | \
xargs -r -0 rm -f
This skips anything under /var/log/nginx.
5.3 Using -exec Instead of xargs
find’s -exec can run a command on each file individually. It’s slower but avoids xargs altogether:
find /var/log -type f -name '*.log' -mtime +30 -exec rm -f {} +
The trailing + tells find to pass as many arguments as possible to a single rm call, similar to xargs.
6. Handling Very Long Filenames
On systems where filenames can exceed the shell’s argument limit, xargs will automatically split the list into smaller batches. The -n option can enforce a maximum number of arguments per invocation:
find /var/log -type f -name '*.log' -mtime +30 -print0 | \
xargs -r -0 -n 100 rm -f
This forces rm to run with at most 100 filenames per call.
7. Logging the Deletion
For audit purposes, it’s handy to record what got deleted. Append the list to a log file before deletion:
find /var/log -type f -name '*.log' -mtime +30 -print0 | \
tee /var/log/clean_logs_$(date +%Y%m%d).log | \
xargs -r -0 rm -f
tee writes the list to a file and passes it through to xargs. The log file name includes the date for easy reference.
8. Common Pitfalls and How to Avoid Them
| Pitfall | What Happens | Fix |
|---|---|---|
Accidentally deleting /var/log itself |
rm -f /var/log removes the directory |
Use -type f to restrict to files. |
Running rm without -f on an empty list |
rm prompts for confirmation |
Use xargs -r to skip if no input. |
Forgetting -print0/-0 with filenames containing spaces |
Some files are missed or mis‑parsed | Always use null separation for safety. |
| Deleting logs that are still in use by a service | Service may crash or lose data | Exclude active directories or stop services first. |
| Running the script as root without logging | Loss of traceability | Log the deletion list or use auditd. |
9. Security Considerations
Running the command as root gives it the power to delete anything. Keep the script in a secure location (/usr/local/bin) and set the right permissions:
chmod 755 /usr/local/bin/clean_logs.sh
If you’re using cron, make sure the crontab entry is owned by the root user and not writable by others. Avoid using sudo in the script; instead, run the cron job as root.
10. Alternatives Worth Knowing
- logrotate: For many services,
logrotatealready handles rotation and deletion. Configure it to keep a certain number of archives or a maximum size. - systemd‑journald: If you use
journald, setSystemMaxUse=in/etc/systemd/journald.confto cap disk usage automatically. - trash-cli: If you prefer a “soft delete” that can be recovered, use
trash-putinstead ofrm.
11. Real‑World Example: A Homelab Server
I run a small NAS with a 2 TB SSD. Over the past year, /var/log grew to 200 MiB because of frequent cron jobs. I set up the script above with a 60‑day threshold and a 5 MiB size filter. After a month, the log directory shrank to 30 MiB, and the SSD health metrics improved.
The cron job runs at 03:00 every Sunday, and I review the daily log file (/var/log/clean_logs_20261109.log) to confirm that only expected files were removed. No service interruptions occurred because I excluded /var/log/nginx and /var/log/mysql.
12. Troubleshooting
-
xargs: rm: No such file or directory
Usually means the file was deleted by another process betweenfindandrm. It’s harmless; you can ignore it or add-fto silence the error. -
find: paths must precede expression
Make sure options like-mtimecome after the path (/var/log). The order matters. -
rm: cannot remove ‘/var/log’: Is a directory
Double‑check you used-type f. If you accidentally omitted it,rmwill try to delete the directory.
13. Wrap‑Up
Using find and xargs to purge old log files is a lightweight, reliable method that fits nicely into a homelab or small server setup. By:
- Crafting a precise search predicate,
- Verifying the list before deletion,
- Using null‑terminated pipelines,
- Automating with cron, and
- Logging the actions,
you keep your log directories tidy without risking accidental data loss. It plays well with built‑in tools like logrotate and journald, giving you full control over what gets deleted and when.
See also
- Why I Swapped Traefik for Nginx Ingress in My Home Reverse‑Proxy Stack.
- Why I Switched from Nginx to Caddy for My Home Reverse Proxy and What I Learned
- When Nginx Reverse‑Proxy Returns 502 on Your Home‑Lab HTTPS Site: A Step‑by‑Step Fix for Certificate and Path Errors
- How to audit stale SSH keys with ssh-keygen‑l and fail2ban to stop brute‑force leaks on production servers
- Automating Let's‑Encrypt renewal on a single‑node web server without downtime