Chmod recursive: change a whole folder without flattening it
A recursive chmod is one of the few commands where the most popular answer online and the correct answer are reliably different. chmod -R 755 and chmod -R 777 get repeated because they do clear the immediate error. What gets left out is that a single mode is being applied to two categories of thing whose permission bits mean different operations, so whatever the mode is right for, it is wrong for the other half of the tree.
Recursion adds three more problems on top of that. The default handling of symbolic links is reversed between a Mac and a Linux box, chmod keeps no record of what it overwrote, and the layers that actually cause most permission failures on a Mac are untouched by a recursive mode change.
One mode, two kinds of thing
The execute bit is the whole difficulty. On a regular file it means the kernel will try to run the file. On a directory it means something unrelated: permission to look names up, so that a path leading through the directory resolves at all. The manual page gives both meanings in a single line for the owner's bit, describing 0100 as "For files, allow execution by owner. For directories, allow the owner to search in the directory."
A directory without that bit is unreachable regardless of what the files inside it allow. Which is why chmod -R 644 is such a reliable way to lock yourself out of your own folder: every directory loses its search bit at once, and the files inside become unreachable even though their own modes look fine.
chmod -R 755 avoids that by going the other way. Every directory gets its search bit, and so does every .txt, .png and .pdf in the tree. Nothing breaks visibly, which is the problem. Tools that check for loose permissions before agreeing to work, most notably SSH with its key files, will refuse rather than warn, and the reason is now buried somewhere in a tree that was changed in bulk.
Three ways to recurse, and what each is for
There are three mechanisms, and the choice between them is about whether files and directories need different treatment.
| Approach | What it does | Use it when |
|---|---|---|
chmod -R <mode> dir |
Applies one mode to every entry | The whole tree genuinely wants the same mode, such as chmod -R go= dir |
chmod -R =rw,+X dir |
Adds execute only where it belongs | A mixed tree of documents and folders |
find dir -type d -exec chmod 755 {} + |
Selects by kind first, then applies | Files and folders need different modes |
The middle row is the capital X, and it exists for exactly this case. The manual page defines it as "the execute/search bits if the file is a directory or any of the execute/search bits are set in the original (unmodified) mode", and adds that it is "only meaningful in conjunction with the op symbol +, and are ignored in all other cases". The mode in that row is the manual page's own example, listed under valid modes as =rw,+X, with the explanation "set the read and write permissions to the usual defaults, but retain any execute permissions that are currently set". Read across a whole tree, that is a description of what a mixed folder of documents and directories usually wants.
The find route is the explicit version. -type d matches directories, -type f matches regular files, and running one pass of each is the unambiguous way to say 755 for folders and 644 for documents.
One detail in that command is worth getting right. -exec utility {} + replaces the braces "with as many pathnames as possible for each invocation of utility", which means a handful of chmod processes rather than one per file. The older -exec ... \; form starts a new process for every single entry. On a large tree the difference is substantial, and the only reason to prefer the semicolon form is a command that can genuinely only take one argument at a time.
find also brings limits that chmod -R has no equivalent for. -maxdepth n descends at most n levels, -mindepth 1 skips the starting directory itself, and -prune stops the descent into whatever matched. Those turn a blanket change into a scoped one.
The symlink default is reversed on a Mac
This is the difference most likely to make a copied command misbehave, and it is easy to miss because the flags have the same names on both systems.
Three options control symbolic links during a recursive run, and they only apply when -R is given. -H follows links named on the command line but not links found while walking. -L follows all of them. -P follows none.
The Mac manual page documents -P and says plainly: "If the -R option is specified, no symbolic links are followed. This is the default." GNU coreutils documents the opposite, stating under -H that "this is the default if none of -H, -L, or -P is specified". The same chmod -R against the same tree therefore treats a symlinked directory given as the argument differently depending on which machine it runs on.
-L deserves a second look before it gets used. Its documented behaviour during a recursive run is that every symbolic link is followed, which means the mode change lands on whatever the links point at, including targets that sit outside the folder named on the command line. In a tree that other accounts can write to, the set of things a single command reaches is therefore not fixed at the moment it is typed.
The Mac manual page carries a separate warning of its own about the argument rather than the traversal: "Beware of unintentionally matching the .. hard link to the parent directory when using wildcards like .*." A command written as chmod -R 755 .* to catch dotfiles can expand to include .. and walk upward out of the intended folder.
Why find has a depth-first option
There is a subtle ordering problem in recursive permission changes, and find has a flag for it that explains itself.
By default find visits a directory before its contents. -depth reverses that, and the manual page gives the reason directly: "It ensures that you have write permission while you are placing files in a directory, then sets the directory's permissions as the last thing."
The same logic applies to tightening permissions. A pass that removes write access from a directory before it has finished with the files inside it can lock itself out partway through. Working from the leaves upward avoids that. chmod -R visits in pre-order and offers no way to change it, so a tightening pass that has to work in the other direction is a find -depth job.
What a recursive mode change will not touch
Four things on a Mac survive chmod -R untouched, and between them they account for most of the permission failures that persist after the command has run.
Access control lists are the first. A recursive mode change rewrites the twelve mode bits and leaves any access control list exactly where it was, so a deny entry keeps denying. Removing them across a tree is a separate run, chmod -R -N dir, using the option the manual page describes as "Removes the ACL from the named file(s)".
File flags are the second. The user immutable flag freezes a file against its own owner, and no mode will override it. Clearing flags across a tree is chflags, which documents the recursive form with an example: "Recursively clear all flags on files and directories contained within the foobar directory hierarchy: chflags -R 0 foobar".
Mount points are the third, and this one is an asymmetry rather than a layer. chflags has a -x option, "Do not cross mount points". chmod has no such option. A recursive chmod that reaches a mounted volume inside the tree descends into it, and an external drive or a disk image attached below the starting folder gets rewritten along with everything else.
The fourth is the system's own protection and the per application privacy controls, and the error text tells them apart. The system error list defines error 13, EACCES, as "Permission denied" and error 1, EPERM, as "Operation not permitted". A recursive run that prints permission denied is hitting mode bits. One that prints operation not permitted is hitting a flag or a protected location, and no mode will move it.
There is no undo
chmod records nothing. The previous modes are gone the moment the command returns, and the Mac implementation has no --reference=ref_file option to copy a known-good mode back from elsewhere. GNU coreutils has that option, which is why restore advice written for Linux often assumes a reference file exists.
Two habits cover the gap. The first is capturing the current state before the change, since ls -lR dir written to a file is enough to reconstruct what a mode used to be. The second is checking the scope before applying anything, by running the find expression on its own first and reading the list of paths it prints. A find command that matches the wrong set is cheap to discover and expensive to discover afterwards.
For a tree already flattened by chmod -R 777, the workable reset is two passes: 755 for directories selected with -type d, 644 for regular files selected with -type f. Anything with stricter requirements, private keys and configuration files in particular, then gets set individually according to whatever tool reads it.
Watching a long run
A recursive change over a large tree gives no output at all, which is indistinguishable from being stuck.
Two things help. -v makes chmod print each filename as it changes, and repeating it as -vv also prints "the old and new modes of the file will also be printed, in both octal and symbolic notation". The manual page does note that -v "is non-standard and its use in scripts is not recommended", so it belongs in interactive checking rather than in automation.
The other is already running without being asked for. The manual page states that "If chmod receives a SIGINFO signal (see the status argument for stty(1)), then the current filename as well as the old and new modes are displayed." The terminal's STATUS character sends that signal, and the terminal documentation gives its default value as ^T. Pressing it during a long run prints which file chmod is on.
The Finder equivalent, and where the windows go
The same job exists without a terminal. Selecting a folder, opening Get Info, expanding Sharing & Permissions and choosing "Apply to enclosed items" from the action menu applies the current settings to everything inside. Apple's four permission levels there are Read & Write, Read only, "Write only (Drop Box)" and No Access. The same panel offers "Revert changes" before the window is closed, which is the one undo that exists anywhere in this workflow.
What it does not offer is any way to treat files and folders differently, which is the whole reason find -type d exists. So the realistic shape of the work is a folder picked in one window, a find expression run in another, and ls -le in a third to check what the mode change did not reach. The commands are short; the switching between windows is not. That is what a file manager with a built-in terminal addresses, by keeping the selection and the command in the same place. The shape of that is in Features, and how it compares with the other options on the Mac is in Compared with other file managers.
What to change first
Run the find expression by itself before attaching -exec, and read the paths it prints. Then use two passes, -type d for directories and -type f for files, rather than one mode for the whole tree, and capture ls -lR first since chmod has nothing to restore from. Keeping the folder and the shell in one window, which is the problem Atriens is built around, is what removes the checking round trips.
Frequently asked questions
What is the correct way to reset a folder after `chmod -R 777`?
Two passes rather than one. find dir -type d -exec chmod 755 {} + for the directories and find dir -type f -exec chmod 644 {} + for the regular files, which gives folders their search bit without leaving documents marked executable. Anything with stricter requirements of its own, such as private keys, then has to be set individually.
Why does `chmod -R` behave differently on a Mac than on Linux?
The default for symbolic links is reversed. The Mac manual page documents -P, following no symbolic links, as the default during a recursive run, while GNU coreutils documents -H, following links named on the command line, as its default. Three GNU options are also missing on a Mac: --reference, --preserve-root and -c.
Does `chmod -R` remove access control lists?
No. It rewrites the mode bits and leaves any access control list intact, so a deny entry keeps applying after the command succeeds. Removing them across a tree needs chmod -R -N dir, and ls -le is how to see whether any are present in the first place.
Is there a way to see progress during a long recursive chmod?
Yes, two. Adding -v prints each filename as it is changed and -vv also prints the old and new modes in octal and symbolic form. Without either, pressing the terminal's STATUS character, ^T by default, sends SIGINFO and chmod prints the file it is currently on.
Will a recursive chmod follow a mounted volume inside the folder?
Yes, and there is no option to stop it. chflags has -x for not crossing mount points, but chmod has no equivalent, so an attached disk or disk image below the starting directory gets rewritten with everything else. Limiting the depth with find -maxdepth or excluding the path with -prune is the way around it.