find . -type f -mtime +30 lists every file not modified in the last 30 days. The +30 reads as "modification age more than 30 days", so the result is every file older than your window. This is the expression cleanup scripts use to clear out stale logs, abandoned temp files, and old build artifacts.
The companion article covers the recent direction (-mtime -N, files modified within a window). This page is the inverse: stale-file detection with +N. The sign is the whole game. -N means recent, +N means old, and bare N means a single 24-hour slice that almost never matches what you want.
Set your values
Set your OS, search path, and the staleness threshold in days. Every find example below updates with your values.
The one-liner
find :search_path -type f -mtime +:daysThat returns every regular file under your search path whose modification time is more than :days days in the past. Drop -type f if you also want to evaluate directories and symlinks.
Why +N is the inverse of -N
-mtime filters by modification age in 24-hour units, rounded toward zero. The sign in front of N picks the comparison direction:
| Expression | Meaning |
|---|---|
-mtime -30 | Modified in the last 30 days (age < 30) |
-mtime +30 | Modified more than 30 days ago (age > 30) |
-mtime 30 | Modified between 30 and 31 days ago (age == 30 after flooring) |
! -mtime -30 | Logical negation: NOT modified in the last 30 days |
The two clean expressions, -mtime -30 and -mtime +30, are not quite a perfect partition. Files whose age floors to exactly 30 match neither, because -30 is strictly less than and +30 is strictly greater than. That 24-hour gap is the off-by-one. If you genuinely need "everything that is not recent", ! -mtime -:days is the airtight negation. It matches age >= 30 including the boundary slice, where -mtime +:days matches only age > 30.
For stale-file cleanup the gap rarely matters. A file sitting right on the 30-day line is borderline stale either way. But know the rule so a 31-day file showing up in a "31 days and older" report does not surprise you.
Minute resolution with -mmin
When 24-hour buckets are too coarse, -mmin +N is the minute-resolution variant. Same sign convention: +N for "more than N minutes ago".
find :search_path -type f -mmin +120-mmin +120 finds files untouched for more than two hours. Useful for catching half-finished uploads or temp files that a job should have cleaned up by now.
Not modified since a specific timestamp
-newer FILE compares against the modification time of a reference file. To express "not modified since timestamp X", touch a marker file at X and negate the test with !:
touch -t 202604010000 /tmp/cutoff-mark
find :search_path -type f ! -newer /tmp/cutoff-marktouch -t YYYYMMDDhhmm sets the marker's modification time to the cutoff. ! -newer /tmp/cutoff-mark then matches every file not newer than that marker, which is exactly "not modified since April 1". This is more precise than -mtime because it skips the day-floor rounding entirely.
-atime vs -mtime: the difference that matters
There are three timestamps on every Unix inode, and they answer different questions:
| Timestamp | find test | Updated when |
|---|---|---|
| mtime | -mtime | File contents change (a write) |
| atime | -atime | File contents are read (an access) |
| ctime | -ctime | The inode changes (chmod, chown, rename, link count) |
For stale-file detection the instinct is often "find files nobody has used lately", which sounds like -atime. A file can have an old mtime but still be read every day, so -mtime alone would flag it as stale when it is actually hot.
-atime is the access-time variant:
find :search_path -type f -atime +:daysThe noatime trap
Here is the catch that ruins more cleanup scripts than any other: atime is often disabled. Updating the access time on every single read is a write amplification problem, so most modern Linux distributions mount filesystems with relatime (atime updated lazily, roughly once a day) or noatime (atime never updated at all). On a noatime mount, -atime returns whatever the access time was frozen at, which usually tracks mtime and gives you a misleading answer.
Check what your mount actually does before trusting -atime:
mount | grep -E 'noatime|relatime'If you see noatime, -atime is unreliable on that filesystem and you should fall back to -mtime, or remount with strictatime if you genuinely need access tracking. relatime is a middle ground: it updates atime only when the previous atime is older than the current mtime, so day-granularity -atime +N queries still work, but anything finer does not. Windows NTFS disables last-access updates by default for the same performance reason, which is why the PowerShell LastAccessTime filter above is just as suspect.
The safe stale-file cleanup pattern
The classic cleanup task is "delete everything older than N days in this directory". The non-negotiable habit is a dry run first. Print, eyeball, then delete.
Step one, the dry run. List exactly what would be removed:
find :search_path -type f -mtime +:days -printStep two, once the list looks right, swap -print for -delete:
find :search_path -type f -mtime +:days -deletefind -delete has no confirmation and no undo. The find and delete files article covers the full safe-delete checklist, including the directory-deletion-order trap where -delete needs -depth to remove children before parents.
Find stale files but keep recently-accessed ones
A smarter cleanup keeps files that were read recently even if they have not been written recently. Combine an old-mtime test with a not-recently-accessed test so a stale-but-still-used file survives the sweep:
find :search_path -type f -mtime +:days -atime +7 -printTwo find tests with no operator between them mean logical AND. A file matches only if it is both older than :days by modification and untouched by reads for more than 7 days. The same noatime caveat applies: if the filesystem does not track atime, the -atime +7 clause is meaningless and you are back to a plain -mtime sweep.
macOS BSD vs GNU find
The staleness-specific differences that bite when a script moves between platforms:
| Behavior | GNU find | BSD find (macOS default) |
|---|---|---|
-mtime +N semantics | Floors age to 24-hour units | Same, with different fractional edge cases |
-atime +N | Supported | Supported |
-mmin / -amin | Supported | Supported |
! -newer FILE | Supported | Supported |
-newerXY (precise comparison) | Supported (rare) | Supported, more variants |
-printf '%A@' (access epoch) | Supported | NOT supported; use -exec stat -f '%a %N' |
-anewer FILE (accessed since) | Supported | Supported |
For cleanup scripts that must run on both Linux and macOS, stay inside the portable set: -mtime, -atime, -mmin, -newer, ! -newer. Skip -printf and any GNU-only format flags.
Common mistakes
1. Confusing mtime, atime, and ctime. -mtime is content writes, -atime is content reads, -ctime is inode metadata changes. A chmod bumps ctime but not mtime. Reading a file bumps atime but not mtime. Pick the timestamp that answers your actual question, not the one that sounds closest.
2. Trusting -atime on a noatime mount. This is the single most common stale-detection bug. On noatime, access time is frozen, so -atime +30 returns files that are read constantly. Always check the mount with mount | grep atime before relying on -atime.
3. Off-by-one with +N. -mtime +30 matches age strictly greater than 30, so a file exactly 30.5 days old has age 30 and does not match. If you need the boundary included, use ! -mtime -30 instead. Same family as the bare-N trap: -mtime 30 is a single 24-hour slice, almost never what a cleanup script wants.
4. Forgetting -type f. Without it, -mtime +30 -delete evaluates directories too. A directory's mtime changes whenever a file is added or removed from it, so an old directory full of fresh files can still match -mtime +30 and trip a delete error. Always pin file-only operations with -type f.
5. Running cleanup against /. find / -mtime +30 -delete is a foot-cannon that will sweep /etc, /usr, and anything else untouched for a month. Always anchor the search at a specific directory and consider adding -xdev to stay on one filesystem.
When NOT to use this
Reach for a different approach when:
- You need to know when a file was last used, not written, and atime is disabled. On a
noatimefilesystem there is no reliable last-access signal. Application-level access logs, or afanotify/auditdwatch, are the only trustworthy sources. - You care about content staleness, not timestamp staleness.
-mtimereflects when the inode's mtime field was last bumped. Atouchwith no write, or anrsyncthat preserves timestamps, can make a file look fresh or stale independent of its contents. For real content drift, compare hashes. - You are detecting abandoned resources, not old files. A config file untouched for two years is not stale, it is stable. Age alone is a weak signal. Pair it with "is anything still referencing this" before you delete.
- The filesystem updates timestamps unreliably. Some FUSE mounts, network filesystems, and container overlay layers report mtime and atime in ways that confuse
find. Verify withstatbefore trusting a sweep.
See also
- find Command Cheat Sheet: the full find reference covering name, type, size, permissions, and exec patterns
- Find files modified in the last 7 days: the inverse and companion to this page, the
-mtime -Nrecent-files direction - Find and delete files safely with find -delete: the cleanup-script playbook with the safe two-step pattern
- External: GNU findutils age ranges, FreeBSD find(1) man page





