TechEarl

How to Find Files by Owner, Group, or Permission with find

find -user www-data lists every file owned by a user; -group developers filters by group; -perm matches the mode bits. The subtle part is -perm -mode (all of these bits set) versus -perm /mode (any of these bits set). Plus the security-audit recipes for world-writable files and SUID/SGID binaries, the BSD vs GNU divergences, and the orphaned-file checks.

Ishan KarunaratneIshan Karunaratne⏱️ 13 min readUpdated
Use find -user, -group, and -perm to locate files by ownership and mode. The -perm -mode vs -perm /mode distinction explained, world-writable and SUID/SGID audit recipes, orphaned-file checks, and the BSD vs GNU find differences on macOS.

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

Try it with your own 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".

bash· Linux (GNU)
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:

bash
find . -type f -uid 33

On 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.

bash· Linux (GNU)
find :search_path -type f -group developers

Query by numeric GID when the group name does not resolve:

bash
find . -type f -gid 1001

Combine -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:

bash
find /var/www -type f -user www-data -group www-data

Find 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.

bash· Linux (GNU)
find :search_path -xdev \( -nouser -o -nogroup \) 2>/dev/null

The -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.

bash· Linux (GNU)
find :search_path -type f -perm 0644

Exact 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.

FormMeaning
-perm MODEPermission bits match MODE exactly
-perm -MODEAll of the bits in MODE are set (others may also be set)
-perm /MODEAny of the bits in MODE is set

Worked example. Say MODE is g+w (the group-write bit):

  • -perm -g+w matches files where the group-write bit is set, regardless of any other bits. Use this for "find everything writable by group".
  • -perm /g+w matches files where the group-write bit is set. With a single bit, -MODE and /MODE behave the same.

The two diverge the moment MODE has more than one bit. Take MODE o+rw (other-read and other-write):

  • -perm -o+rw matches only files where both the other-read and other-write bits are set.
  • -perm /o+rw matches 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.

bash· Linux (GNU)
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:

bash
find / -xdev -type d -perm -o+w ! -perm -1000 2>/dev/null

The ! -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.

bash· Linux (GNU)
find / -xdev -type f -perm -4000 2>/dev/null

-perm -4000 is the SUID bit. For SGID, use -perm -2000:

bash
find / -xdev -type f -perm -2000 2>/dev/null

To 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):

bash
find / -xdev -type f -perm /6000 2>/dev/null

Save 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.

bash· Linux (GNU)
find /var/www -type f ! -user :owner

To list and fix in one pass, hand the results to chown:

bash
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.

BehaviorGNU findBSD find (macOS default)
-perm -MODE (all bits set)SupportedSupported
-perm /MODE (any bit set)SupportedSupported
-perm +MODE (any bit set, legacy)Removed; use /MODEStill accepted
Symbolic modes (-perm -g+w)SupportedSupported
-user / -group by nameSupportedSupported
-uid / -gid by numberSupportedSupported
-nouser / -nogroupSupportedSupported
Default web user namewww-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 -R and chown -R apply a mode or owner across a tree directly. Use find -perm ... -exec chmod only when you need to change some files, filtered by a condition chmod -R cannot express.
  • You want a full security posture, not a single check. find -perm -4000 enumerates SUID binaries, but a real audit wants a hardened baseline. Tools like lynis, 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-Acl and icacls are the right tools; a -perm translation will always be approximate. WSL gives you real GNU find if you genuinely need POSIX semantics.
  • You need to track permission changes over time. find gives you a snapshot. For "what changed since yesterday", a file-integrity monitor like AIDE or Tripwire stores a baseline and diffs against it.

See also

FAQ

TagsfindpermissionsCLILinuxmacOSBSDSecuritySUID
Share
Ishan Karunaratne

Ishan Karunaratne

Tech Architect · Software Engineer · AI/DevOps

Tech architect and software engineer with 20+ years across software, Linux systems, DevOps, and infrastructure — and a more recent focus on AI. Currently Chief Technology Officer at a tech startup in the healthcare space.

Keep reading

Related posts

Use find -type f -name '*.txt' for one extension, or group -name tests in escaped parens joined by -o for many (.jpg, .png, .gif). Case-insensitive -iname, files with no extension, the -regex shortcut, and BSD vs GNU find differences.

How to Find Files by Extension (One or Many) with find

find . -type f -name '*.txt' lists every file with one extension. For many extensions you group -name tests with escaped parens and join them with -o. This covers the single one-liner, the multi-extension OR pattern, why the parens are mandatory, case-insensitive -iname, files with no extension at all, the -regex shortcut, and the BSD vs GNU divergence that bites on macOS.

Use find -size +100M to list files larger than 100 megabytes. Unit suffixes (c/k/M/G), +/- sign convention, combine with sort -rn to surface the biggest files on disk, and BSD vs GNU rendering differences.

How to Find Files Larger Than a Size with find -size

find . -size +100M lists every file larger than 100 megabytes. The unit suffixes (c, k, M, G), the +/- sign convention, how to combine with sort to find the biggest files on disk, the BSD vs GNU divergence for printing sizes, and the wc -c trick for byte-exact thresholds.

Use find ... -print0 | xargs -0 grep -l 'PATTERN' to find every file containing a string. When grep -r is enough, when to add find as a pre-filter for performance, multi-pattern matching with -E, and the safe NUL-delimited pipeline.

How to Find Files Containing Specific Text (find + grep)

find ... -print0 | xargs -0 grep -l 'PATTERN' finds every file containing a piece of text. The combo handles weird filenames, scales to huge trees, and replaces three other common but broken pipelines. When to use grep -r alone, when to add find as a pre-filter, and the BSD vs GNU pitfalls.