find . -user www-data lists every file owned by a given user. find . -group developers filters by group. find . -perm 0644 matches files with an exact permission mode. Those three tests cover most ownership and permission questions, and they are the backbone of every web-root audit and security scan I run.
The part that trips people up is the -perm mode syntax. -perm 0644 is an exact match. -perm -mode means "all of these bits set". -perm /mode means "any of these bits set". Get those mixed up and a world-writable-file scan quietly returns the wrong list. This page is the reference I keep open while auditing file ownership and permissions.
Set your values
Set your OS, search path, and the owner name. Every find example below updates with your values.
Find files by owner
The -user test matches files owned by a named user. This is the first thing I reach for during a web-root audit: "show me everything the app user owns".
find :search_path -type f -user :owner-user accepts a name or a numeric UID. If the user no longer exists in /etc/passwd (a deleted account), the name lookup fails, so query by UID directly with -uid:
find . -type f -uid 33On Linux the conventional web-server user is www-data (Debian/Ubuntu) or apache / nginx (RHEL family). On macOS it is _www. The underscore-prefixed name is a macOS convention for system service accounts, and it is the single most common reason a copy-pasted Linux command finds nothing on a Mac.
Find files by group
-group is the group-ownership equivalent. It accepts a group name or, with -gid, a numeric group ID.
find :search_path -type f -group developersQuery by numeric GID when the group name does not resolve:
find . -type f -gid 1001Combine -user and -group to match files owned by a specific user and group. Tests are ANDed together by default, so listing them in sequence is all it takes:
find /var/www -type f -user www-data -group www-dataFind orphaned files (no valid user or group)
When you delete a user account, the files that account owned do not vanish. They keep the now-dangling numeric UID. -nouser matches any file whose UID has no entry in the password database; -nogroup does the same for the group database.
find :search_path -xdev \( -nouser -o -nogroup \) 2>/dev/nullThe -o operator is a logical OR, so \( -nouser -o -nogroup \) matches files that are orphaned in either dimension. The parentheses must be escaped (or quoted) because they are shell metacharacters. Orphaned files are a routine finding after offboarding, and they are worth cleaning up because a future account that reuses the same UID would silently inherit ownership.
Find files by exact permission
-perm 0644 matches files whose permission bits are exactly 0644: owner read/write, group read, other read. Nothing more, nothing less.
find :search_path -type f -perm 0644Exact matching is strict. A file at 0664 will not match -perm 0644, even though both are "readable by everyone". Exact mode is what you want for compliance checks like "every config file must be exactly 0600", and it is the wrong tool for "find anything writable by group", which is what the next section covers.
The subtle part: -perm -mode versus -perm /mode
This is the distinction that matters most, and the one most people get wrong.
| Form | Meaning |
|---|---|
-perm MODE | Permission bits match MODE exactly |
-perm -MODE | All of the bits in MODE are set (others may also be set) |
-perm /MODE | Any of the bits in MODE is set |
Worked example. Say MODE is g+w (the group-write bit):
-perm -g+wmatches files where the group-write bit is set, regardless of any other bits. Use this for "find everything writable by group".-perm /g+wmatches files where the group-write bit is set. With a single bit,-MODEand/MODEbehave the same.
The two diverge the moment MODE has more than one bit. Take MODE o+rw (other-read and other-write):
-perm -o+rwmatches only files where both the other-read and other-write bits are set.-perm /o+rwmatches files where either the other-read or the other-write bit is set.
So -MODE is the "all of these" filter and /MODE is the "any of these" filter. For most security scans you want -MODE, because "all of the dangerous bits are set" is the precise question. Octal works in both forms too: -perm -0002 is "world-writable bit set", identical to -perm -o+w.
There is an older third syntax, -perm +MODE, which meant "any of these bits set". GNU find removed it (it collided with the octal-with-leading-plus parsing) and replaced it with /MODE. BSD find on macOS still accepts +MODE. If you see -perm +MODE in an old script, it means the same as /MODE on GNU.
Security audit: world-writable files
A world-writable file is one any user on the system can modify. In a web root or a config directory that is almost always a misconfiguration. The audit recipe scans for the other-write bit.
find / -xdev -type f -perm -o+w 2>/dev/null-perm -o+w (equivalently -perm -0002) matches "the world-write bit is set". -xdev keeps the scan on the root filesystem instead of wandering into network mounts, /proc, and /sys. 2>/dev/null discards the permission-denied noise you get when scanning / as a non-root user. Run it under sudo for a complete picture.
World-writable directories are a separate concern and often legitimate (/tmp is world-writable by design, protected by the sticky bit). To audit world-writable directories that are missing the sticky bit:
find / -xdev -type d -perm -o+w ! -perm -1000 2>/dev/nullThe ! -perm -1000 excludes any directory that already has the sticky bit set.
Security audit: SUID and SGID binaries
A SUID (set-user-ID) binary runs with the privileges of its owner, not the user who launched it. SGID (set-group-ID) does the same for the group. A handful of SUID-root binaries are normal (sudo, passwd, ping). An unexpected one is a privilege-escalation vector, so enumerating them is a standard hardening step.
find / -xdev -type f -perm -4000 2>/dev/null-perm -4000 is the SUID bit. For SGID, use -perm -2000:
find / -xdev -type f -perm -2000 2>/dev/nullTo catch both in one pass, OR them together. -perm /6000 matches "the SUID or SGID bit is set" (this is exactly the case where /MODE is the right form, because you want either bit):
find / -xdev -type f -perm /6000 2>/dev/nullSave the baseline output somewhere, then diff against it on a schedule. A new SUID binary appearing between two scans is a finding worth investigating immediately.
Audit: files NOT owned by the expected user
For a web root, every file should usually belong to one app user. The negation ! -user (or -not -user) surfaces anything that does not, which is how you catch a file accidentally created as root during a deploy.
find /var/www -type f ! -user :ownerTo list and fix in one pass, hand the results to chown:
find /var/www -type f ! -user www-data -exec chown www-data:www-data {} +As always with a destructive -exec, run it once with -print instead of -exec to eyeball the list, then swap it in. The same negation works for groups: ! -group www-data finds files with an unexpected group owner.
macOS BSD vs GNU find
The ownership and permission tests are mostly portable, but a few details diverge between the GNU find on Linux and the BSD find macOS ships.
| Behavior | GNU find | BSD find (macOS default) |
|---|---|---|
-perm -MODE (all bits set) | Supported | Supported |
-perm /MODE (any bit set) | Supported | Supported |
-perm +MODE (any bit set, legacy) | Removed; use /MODE | Still accepted |
Symbolic modes (-perm -g+w) | Supported | Supported |
-user / -group by name | Supported | Supported |
-uid / -gid by number | Supported | Supported |
-nouser / -nogroup | Supported | Supported |
| Default web user name | www-data / apache / nginx | _www |
The portable subset is -perm -MODE, -perm /MODE, -perm MODE, -user, -group, -uid, -gid, -nouser, -nogroup. Avoid -perm +MODE: it works on macOS and fails on modern GNU find. If a script must run on both, write /MODE.
Common find permission mistakes
1. Confusing -perm -MODE and -perm /MODE. -MODE is "all of these bits set", /MODE is "any of these bits set". For a single bit they behave identically, which lulls people into thinking they are interchangeable. They are not. A world-writable scan wants -perm -o+w; an "any dangerous bit" scan wants -perm /MODE.
2. Using exact -perm MODE when you meant a partial match. -perm 0644 will miss a 0664 file. If the question is "is the group-write bit set", use -perm -g+w, not an exact mode.
3. Mixing octal and symbolic without thinking. -perm -4000 and -perm -u+s both match the SUID bit. Octal is denser; symbolic is more readable. Pick one per script. The bug is writing -perm 4000 (exact) when you meant -perm -4000 (bit set).
4. Forgetting -xdev on a full-filesystem scan. find / -perm -o+w without -xdev wanders into /proc, /sys, network mounts, and container overlay filesystems. The scan takes far longer and the results are full of noise. Always add -xdev when starting at /.
5. Running an ownership scan as the wrong user. Scanning / as a non-root user produces a flood of permission-denied errors and an incomplete result. Either run under sudo or accept that the scan only covers what your user can read, and append 2>/dev/null so the errors do not bury the real output.
6. Assuming -user works for a deleted account. Once the account is gone from /etc/passwd, -user name fails to resolve. Query by -uid with the raw number, or use -nouser to find everything the account left behind.
When NOT to use this
Reach for a different tool when:
- You need a recursive permission change, not a search.
chmod -Randchown -Rapply a mode or owner across a tree directly. Usefind -perm ... -exec chmodonly when you need to change some files, filtered by a conditionchmod -Rcannot express. - You want a full security posture, not a single check.
find -perm -4000enumerates SUID binaries, but a real audit wants a hardened baseline. Tools likelynis,chkrootkit, or a CIS-benchmark scanner cover ownership, permissions, and dozens of other controls in one pass. - You are on Windows. POSIX permission bits do not map cleanly onto Windows ACLs.
Get-Aclandicaclsare the right tools; a-permtranslation will always be approximate. WSL gives you real GNUfindif you genuinely need POSIX semantics. - You need to track permission changes over time.
findgives you a snapshot. For "what changed since yesterday", a file-integrity monitor likeAIDEorTripwirestores a baseline and diffs against it.
See also
- find Command Cheat Sheet: the full find reference covering name, type, size, time, and exec patterns
- Find files by extension: the
-nameand-inamereference for extension-based searches - Find and delete files safely with find -delete: the cleanup-script playbook for acting on matched files
- External: GNU findutils manual, FreeBSD find(1) man page.





