A Familiar Story: Disk Full at 2 AM
Back when I was managing an old CentOS 7 server at work, I ran into this more times than I care to remember: the alarm going off at 2 AM, /var disk at 98%, the application starting to throw errors. I’d open a terminal, run df -h, then spend ages poking through directories one by one with ls -lh. It took over an hour to track down a pile of log files from six months ago eating nearly 40GB.
If I’d known how to use find properly from the start, that whole ordeal would have taken 30 seconds. Not basic find like find /var -name "*.log" — the full version: combining multiple criteria, filtering by time and size, then piping straight into xargs to handle everything in one shot.
Why Basic find Falls Short
For the first few years I was only using find at this level:
find /var/log -name "*.log"
Real problems aren’t that simple. You don’t just need to find .log files — you need to find .log files older than 30 days, larger than 100MB, and owned by the nginx user. Run the command above and you get a few hundred results — old and new, large and small — and then you’re stuck filtering by hand.
And once you’ve found them, then what? Copy each line and rm it one by one? What if there are 200 files? Advanced find solves both problems — precise filtering upfront, then immediate processing all in the same command.
Advanced Filtering Options for find
Filter by Time
This is the first group I reach for every time I clean up a server:
-mtime +N— files modified more than N days ago-mtime -N— files modified within the last N days-atime +N— files not accessed in more than N days-newer file_ref— files newer than a reference file
# Find log files older than 30 days
find /var/log -name "*.log" -mtime +30
# Find files not accessed in 60 days
find /home -atime +60 -type f
Filter by Size
Units can be c (bytes), k (KB), M (MB), G (GB):
# Find files larger than 100MB
find /var -size +100M -type f
# Find files smaller than 1KB (usually empty junk files)
find /tmp -size -1k -type f
Filter by Permissions and Ownership
# Find files with SUID bit — important for security
find / -perm -4000 -type f 2>/dev/null
# Find files owned by the nginx user
find /var/log -user nginx -type f
# Find files with 777 permissions (usually a config mistake)
find /var/www -perm 777 -type f
Combining Multiple Conditions
Not many people tap into this part — but this is where find really gets powerful. It supports logical operators: -and (default), -or, and -not:
# Implicit AND: .log files AND larger than 50MB AND older than 7 days
find /var/log -name "*.log" -size +50M -mtime +7
# OR: find .log or .gz files
find /var/log \( -name "*.log" -o -name "*.gz" \) -mtime +30
# NOT: find files that are not .log
find /var/log -not -name "*.log" -type f
# Complex combination: .log or .txt files, older than 14 days, larger than 10MB
find /data \( -name "*.log" -o -name "*.txt" \) -mtime +14 -size +10M
Processing Results: -exec vs xargs
Using -exec: Simple and Safe with Special Filenames
-exec runs a command for each file found. {} is the placeholder for the filename, terminated with \;:
# Delete log files older than 30 days
find /var/log -name "*.log" -mtime +30 -exec rm -f {} \;
# Change permissions of all PHP files to 644
find /var/www -name "*.php" -exec chmod 644 {} \;
# Print details of each file found
find /tmp -size +10M -exec ls -lh {} \;
# Compress old log files instead of deleting
find /var/log -name "*.log" -mtime +7 -exec gzip {} \;
Replace \; with + to batch multiple files into a single invocation — significantly faster since it reduces the number of process forks:
# Call rm once with multiple files (more efficient)
find /var/log -name "*.log" -mtime +30 -exec rm -f {} +
Using xargs: More Flexibility When You Need Control
Pipe results to xargs when you need to limit how many files are processed at once, or run multiple processes in parallel:
# Pipe find results to xargs rm
find /var/log -name "*.log" -mtime +30 | xargs rm -f
# Safer with filenames containing spaces: use -print0 and -0
find /data -name "*.tmp" -print0 | xargs -0 rm -f
# Parallel execution with -P: process 4 files at a time
find /backup -name "*.sql" -print0 | xargs -0 -P 4 gzip
# Limit files per invocation: -n 10
find /logs -name "*.log" -print0 | xargs -0 -n 10 rm -f
When to Use Which?
- Use
-exec {} \;when the command needs to process each file individually (likegzip, ormvto a specific directory) - Use
-exec {} +orxargswhen the command accepts multiple arguments at once (likerm,chmod) — fewer forks, noticeably faster - Use
xargs -0combined with-print0when filenames might contain spaces or special characters — this is best practice on production systems
Complete Pipelines for Real-World Scenarios
Scheduled Server Log Cleanup
This is exactly the script I wrote after that late night. It runs via cron every week, and I’ve never had the disk fill up from logs since:
#!/bin/bash
# Delete logs older than 30 days, larger than 1MB
find /var/log -name "*.log" -mtime +30 -size +1M -print0 | xargs -0 rm -f
# Compress logs from 7-30 days old (keep but save space)
find /var/log -name "*.log" -mtime +7 -mtime -30 -size +1M -print0 | xargs -0 gzip
# Delete .gz files older than 90 days
find /var/log -name "*.gz" -mtime +90 -delete
Security Audit: Finding Dangerous Files
# Find all files with SUID/SGID — run after installing new packages
find / -type f \( -perm -4000 -o -perm -2000 \) 2>/dev/null | sort
# Find world-writable files in web root
find /var/www -perm -o+w -type f
# Find PHP files created in the last 24h — detect webshells
find /var/www -name "*.php" -mtime -1 -type f
Batch Rename or Move Files
# Move image files older than 1 year to the archive directory
find /uploads -name "*.jpg" -mtime +365 -exec mv {} /archive/images/ \;
# Bulk change file ownership
find /var/www/html -type f -exec chown www-data:www-data {} +
# Find and delete empty directories
find /data -type d -empty -delete
The Pattern to Remember for find on Production
The structure I use most often:
find [path] [filter_conditions] -print0 | xargs -0 [processing_command]
Always use -print0 | xargs -0 instead of a regular pipe — it’s safer with unusual filenames. Before actually deleting or modifying anything, replace the final command with ls -lh to preview the list first. On production, a misplaced rm command has no undo.
Maintenance tasks that used to take an hour are now a 5-line cron script. find isn’t complicated — you just need the right syntax.
