Delete a folder in Linux with rmdir, rm -r or find: dry runs, old files, Permission denied, busy, full disk. Tested on Ubuntu 24.04.
To delete a folder in Linux you use one of two commands: rmdir for an empty directory, rm -r for a directory with files inside. Both are final: there is no recycle bin on a server, and a mistyped path or an empty variable can wipe far more than you meant. Below is the short answer, then every variant that people actually search for: removing by pattern or by age, keeping a few files, fixing the "Directory not empty", "Permission denied" and "Device or resource busy" errors, freeing a full disk, and the habits that keep rm -rf from ruining your day.
TL;DR. Empty directory:
rmdir name. Directory with contents:rm -r name. Add-fonly to silence "no such file" and read-only prompts, never to skip thinking. Before you delete, look:ls -la name,du -sh name,find name | head. To delete by age usefind DIR -type f -mtime +30 -delete, and run it once withrm -rf $VAR/with a variable that may be empty, and do not typesudountil the plain command has failed for a reason you understand.
Everything below was run on a test VPS with Ubuntu 24.04.5, GNU coreutils 9.4 (rm, rmdir) and findutils 4.9.0 (find), on an ext4 file system, inside a throwaway directory /tmp/rmlab that we deleted at the end. The outputs are copied from that run. Other systems (BSD, macOS, BusyBox, other file systems) differ in details; we did not test them. Dangerous examples with an empty variable were only echoed, never executed with rm.
rmdir deletes a directory only if it is empty. That is its whole point: it cannot destroy data by accident, so it is the safest tool when you expect nothing inside.
mkdir empty-dirrmdir empty-dirNo output means success. Run it again and you see what a failure looks like:
rmdir: failed to remove 'empty-dir': No such file or directoryTo remove a directory together with its empty parents, add -p. We created a/b/c and ran rmdir -p a/b/c: all three levels disappeared, and ls -d a confirmed that a was gone too. rmdir -p stops at the first parent that still has something in it.
If you are cleaning up a list of directories and want rmdir to skip the non-empty ones quietly, use --ignore-fail-on-non-empty. We ran it on a directory containing one file: no error, and the file was still there.
rm removes files. Given a directory, plain rm refuses:
rm d1rm: cannot remove 'd1': Is a directoryThe flag -r (also written -R or --recursive) tells rm to go inside, delete everything it finds, then delete the directory itself:
rm -r d1No output, exit code 0, directory gone. The flag -f (--force) changes three things: it does not ask before deleting write-protected files, it does not complain about paths that do not exist, and so it does not return a non-zero code for a missing path. We deleted d1 a second time:
rm -r d1rm: cannot remove 'd1': No such file or directoryrm -rf d1(no output, exit code 0)This silence is exactly why rm -rf is dangerous in scripts: a typo in the name does not produce an error, it just does nothing, or deletes the wrong thing without a word. Use -f when you really want "make sure this is gone", not as a habit.
One more option worth knowing: rm -d (--dir) removes an empty directory, like rmdir, without descending. On a non-empty one it says rm: cannot remove 'd1': Directory not empty, which makes it a safer cousin of rm -r.
To watch what is being deleted, add -v:
rm -rv project/build/tmpremoved 'project/build/tmp/a.o'removed 'project/build/tmp/b.o'removed directory 'project/build/tmp'Our test results in one table. "Contents" means files and subdirectories inside.
Command | Empty directory | Directory with contents | Path that does not exist |
|---|---|---|---|
| Removed | Error: Directory not empty | Error: No such file or directory |
| Error: Is a directory | Error: Is a directory | Error: No such file or directory |
| Removed | Error: Directory not empty | Error: No such file or directory |
| Removed | Removed with everything inside | Error: No such file or directory |
| Removed | Removed, no questions asked | Silent, exit code 0 |
| Removed | Removed with everything inside ( | Error: No such file or directory |
The cheapest insurance is to look first. We built a small project with a 20 MB file inside build. Three questions to answer before any rm -r: what is in there, how big is it, and what exactly will the command match.
ls -la project project/builddu -sh project project/build project/srcfind project | sortfind project -type f | wc -lls -la lists everything including hidden entries, du -sh prints the total size in human units (a path already counted inside an earlier argument is not counted again, so nested paths show as one line), find project walks the tree and prints every path, and wc -l counts the files. The tail of our output:
21M projectprojectproject/buildproject/build/big.binproject/build/tmpproject/build/tmp/a.oproject/build/tmp/b.oproject/srcproject/src/main.c4Three more habits:
echo in front of the command: echo rm -r project/build prints the command after the shell has expanded variables and wildcards. What you see is what rm would receive.rm -ri DIR asks before descending into each directory and before each file. rm -rI DIR (capital I) asks once. On our run they printed rm: descend into directory 'project/build'? and rm: remove 1 argument recursively? and waited for an answer; we answered n, nothing was deleted.find command with -print, read the list, and only then replace -print by -delete or -exec rm. Examples follow.The shell, not rm, expands * and ? into a list of names before rm starts. So you can see the list for free with echo. We made logs/2024-01, logs/2024-02, logs/2025-01 and logs/keep:
echo logs/2024-*rm -r logs/2024-*ls logslogs/2024-01 logs/2024-02(rm prints nothing)2025-01keepTwo traps. First, the star does not match names that start with a dot. In a directory holding .hidden and visible, rm -r lab-dot/* removed only visible and ls -A still showed .hidden. Delete hidden entries with rm -r lab-dot/.[!.]*, which matches dot-names but not . and ... Second, a pattern that matches nothing is passed to rm as the literal text, so always look at the echo output.
Ask find to list the entries one level deep (-mindepth 1 -maxdepth 1) that are not named keep.txt (! -name keep.txt). In our keepdir there was keep.txt, a.log, b.log, c.tmp and a subdirectory sub:
find keepdir -mindepth 1 -maxdepth 1 ! -name keep.txt -printkeepdir/a.logkeepdir/b.logkeepdir/subkeepdir/c.tmpThe list is right, so we reuse the same selection and change the action. -exec rm -r {} + runs rm -r on the matches, several at a time:
find keepdir -mindepth 1 -maxdepth 1 ! -name keep.txt -exec rm -r {} +ls -A keepdirkeep.txtIn bash there is a shorter way: after shopt -s extglob (enable it on a separate line before the command, not on the same line), the pattern !(keep.txt) means "everything except". In a directory with keep.txt, a.log and b.log, echo !(keep.txt) printed a.log b.log and rm -- !(keep.txt) left only keep.txt. It works in an interactive bash or a script that enables it first; it does not work in plain sh. It also skips hidden files like any wildcard.
This is the classic cleanup of caches, uploads and old logs. -mtime +30 means "last modified more than 30 full days ago". Test data: three files dated 45 days back, two dated two days back.
find cache -type f -printf '%TY-%Tm-%Td %p\n' | sort2026-09-25 cache/c.tmp2026-09-25 cache/old/a.tmp2026-09-25 cache/old/b.tmp2026-11-07 cache/e.tmp2026-11-07 cache/new/d.tmp-printf with %T shows modification dates, so you can see what the age filter works on. Now the dry run, then the real thing:
find cache -type f -mtime +30 -printfind cache -type f -mtime +30 -delete -printBoth printed the same three paths (cache/c.tmp, cache/old/a.tmp, cache/old/b.tmp); the second also deleted them. Here -type f limits the match to regular files, so directories are not touched. The old directory is now empty, and find cache -type d -empty -print lists exactly it. Delete empty directories with -delete in the same way, and the cleanup is complete:
find cache -type d -empty -deletefind cache | sortcachecache/e.tmpcache/newcache/new/d.tmpMinutes instead of days: -mmin +90. Files not read for a while: -atime, but many systems mount with options that make access times unreliable, so prefer -mtime. To make such a cleanup run on a schedule, see systemd timers vs cron.
find evaluates its expression left to right, and -delete is an action that fires for every file that reached it. Put it before the filter and the filter comes too late. In a directory with keep.conf and x.log:
find gotcha -name '*.log' -printfind gotcha -print -name '*.log'gotcha/x.loggotchagotcha/keep.confgotcha/x.logThe second command printed everything because -print came before -name. We then ran find gotcha -delete -name '*.log' on this throwaway directory: it removed keep.conf, x.log and the directory gotcha itself. Rule: filters first, -delete last, and always the same command with -print before it. The reverse mistake also exists: find ne -type d -name inner -delete on a directory with a file inside fails with find: cannot delete 'ne/inner': Directory not empty, because the filter skipped the file. Without a filter, find ne -delete removes the whole tree, children before parents.
The full message from our run:
rmdir: failed to remove 'site': Directory not emptyThe directory has something inside, possibly hidden. Check with ls -A site (capital A shows dot-files without . and ..). Our site held index.html and assets; going deeper, rmdir site/assets/img failed the same way because logo.png was inside. Either empty it, or decide that the contents may go and use rm -r site. Do not reach for rm -rf just because rmdir complained: that complaint was the system telling you the folder is not empty.
Deleting a file is not controlled by the permissions on the file, but by the permissions on the directory that contains it. You need write and execute permission on that directory. We reproduced two common cases as an unprivileged user (nobody).
Case one: a directory without write permission (chmod 555) with a file inside.
rm -r lockedrm: cannot remove 'locked/f': Permission deniedrmdir lockedrmdir: failed to remove 'locked': Directory not emptyYou own it, so give yourself write permission on the whole tree and retry: chmod -R u+w locked && rm -r locked. It worked.
Case two: a directory created by root inside a shared one.
rm -r rootsubrm: cannot remove 'rootsub/file': Permission deniedHere the owner is someone else. Find out who with ls -ld rootsub. If the data is really yours to delete (for example files created by a Docker container running as root), repeat the command with sudo: sudo rm -r rootsub. Run the ls, du and find -print checks first, because sudo removes the last safety net: root bypasses the permission checks that just saved you from a typo. If you are not sure what the permission bits mean, ls -l shows them and man chmod explains them.
These two are different from "Permission denied": even root gets them.
Operation not permitted on a file you own, or as root, usually means the file has the immutable attribute (the append-only attribute +a or the sticky bit on the directory give the same message). We set it with chattr +i:
lsattr imm/f----i---------e------- imm/frm -r immrm: cannot remove 'imm/f': Operation not permittedchattr -i imm/f && rm -r immAfter chattr -i the removal worked. The attribute exists on ext4; other file systems may not support it.
Device or resource busy appears when the directory is a mount point. We mounted a small tmpfs on busy/mnt:
rmdir busy/mntrmdir: failed to remove 'busy/mnt': Device or resource busyumount busy/mnt && rmdir busy/mnt busyCheck with findmnt -T DIR or mount | grep DIR before you delete a directory that someone else set up. Do not run rm -rf on a mounted directory: it deletes the contents of whatever is mounted there (a data disk, a network share), not just the empty mount point. In our test the contents were gone first, and then rm printed "Device or resource busy" because the mount point itself stays. The --one-file-system option skips mounts inside the tree, but it does not help when the argument itself is the mount point. Unmount first, then remove the empty directory.
When a directory has hundreds of thousands of files, rm * can fail before it starts, because the shell expands the star into a command line longer than the system allows. We created 200,000 files:
rm many/*/usr/bin/rm: Argument list too longfind does not build a giant command line; it deletes as it walks:
time find many -type f -deletereal 0m2.670suser 0m0.248ssys 0m2.113sOn our test VPS, 200,000 empty files went in under three seconds. Then rmdir many finished the job. rm -r many should also work, because it receives one argument (we did not run it; the limit applies to the total size of arguments, ARG_MAX is 2097152 bytes on this server). If the real problem is that deleting does not free space and you see "No space left on device" with free gigabytes, the cause may be inodes: see No space left on device: inodes.
On a server the usual reason to delete something big is a full disk. Do not guess: find the biggest directory, then delete it. We made hog/a (60 MB) and hog/b (10 MB):
du -h --max-depth=1 hog | sort -hfind hog -type f -size +20M -exec ls -lh {} +11M hog/b61M hog/a71M hog-rw-r--r-- 1 root root 60M Nov 9 15:08 hog/a/big1du --max-depth=1 prints the size of each immediate subdirectory, sort -h sorts human-readable sizes so the biggest is last, and the second command lists single files over 20 MB. On a real server start from / with du -xh --max-depth=1 / (-x stays on one file system) and go down level by level. Do not delete system directories such as /usr or /var/lib to "make room"; you are looking for logs, caches, old backups and uploads. For Docker images, volumes and container logs there is a dedicated guide: Docker filled the disk: prune and logs.
A file that is deleted while a process still has it open keeps its space until that process closes it or exits. We created a 100 MB file, started tail -f on it, then deleted it:
df -m /tmp/rmlab (before): used 6428 MBrm held.bindf -m /tmp/rmlab (after): used 6428 MBls -l /proc/PID/fd | grep heldlr-x------ 1 root root 64 Nov 9 15:08 3 -> /tmp/rmlab/held.bin (deleted)lsof +L1 | grep rmlabtail 68131 104857600 (deleted)kill PID, then df again: used 6328 MBThe number did not move until we killed the process. lsof +L1 lists open files with zero links, which is exactly deleted-but-held files; the marker (deleted) is also visible in /proc/PID/fd. The fix is to restart or stop the program that holds the file (a log writer is the usual suspect), or to truncate a big log instead of deleting it: : > /path/to/big.log empties it in place. Which one is safe depends on the program; for a service managed by systemd, a restart is the clean way.
Spaces split the command into separate arguments. Without quotes rm -r My Folder looks for two paths called My and Folder:
rm -r My Folderrm: cannot remove 'My': No such file or directoryrm: cannot remove 'Folder': No such file or directoryrm -r 'My Folder'That time nothing was lost, but if a directory called My had existed, it would have gone. Quote paths always, or use the Tab key to complete them. A name that begins with a dash is read as an option:
rm -r -oldstuffrm: invalid option -- 'o'Try 'rm ./-oldstuff' to remove the file '-oldstuff'.rm -r -- -oldstuffThe double dash -- means "options end here"; ./-oldstuff works as well.
A symbolic link is a small file that points to a directory. To remove the link, use rm on it, without -r and without a trailing slash:
ln -s real linkrm linkls realimportant.txtThe target survived. rm -r link without the slash also removed only the link in our test, and rmdir link says Not a directory. The dangerous one is the trailing slash that shell completion adds when you press Tab on a link. In the same setup:
rm -r link/rm: cannot remove 'link/': Not a directoryls -ld linklrwxrwxrwx 1 root root 4 Nov 9 15:09 link -> realls real(empty)The error looks harmless, but the link was still there and the target directory real had been emptied. With a trailing slash, GNU rm (coreutils 9.4; other versions may behave differently) on our system followed the link, deleted the contents of the target, and only then failed on the link itself. rm -rf link2/ did the same to another target. If you only need to unlink, delete the link name without a slash. A dangling link (pointing nowhere) is removed by a plain rm too.
Most disasters come from a variable that is empty. A script says rm -rf "$BUILD_DIR/" and BUILD_DIR was never set. We reproduce only the expansion, with echo:
DIR=''; echo "would run: rm -rf $DIR/"would run: rm -rf /DIR=''; echo "would run: rm -rf $DIR/*"would run: rm -rf /*The first form targets the root directory. Modern GNU rm has a safeguard, --preserve-root, that refuses to operate recursively on / by default; we did not run it to see the message. The second form is not covered by that safeguard on its own: the shell expands /* into every top-level directory before rm sees them, so rm gets /bin /boot /etc /home ... as separate arguments. Do not rely on a guard that you have not read about in your own version's manual.
Protection costs one line each:
set -u at the top of a script makes the shell stop on an unset variable. Our test: bash: line 1: UNSETVAR: unbound variable, exit code 127 (a non-zero code in any case).${DIR:?message} stops if DIR is unset or empty: bash: line 1: DIR: refusing empty DIR. Write rm -rf "${DIR:?}/" or, better, rm -rf -- "${DIR:?}" without the slash.cd /some/dir && rm -rf ./*, never cd /some/dir; rm -rf *. With ; a failed cd leaves you in the old directory and the command runs there. We tested the && form with a missing directory and an echo on the right: cd failed and the right-hand side did not run.find -print habit from above.Do not count on it. rm unlinks the names; there is no trash bin to restore from. We made a directory with a note inside, removed it, and ls answered No such file or directory. Specialised tools can sometimes find the data blocks on an unmounted disk, but the chance falls with every write after the deletion, and it depends on the file system and the disk, so we do not promise it. Real protection is a backup you have restored at least once. If you have just deleted something important, stop writing to that disk (stop the services that log to it) and ask whether a snapshot or backup exists.
Two common cushions.
Interactive alias. Put alias rm='rm -i' in your ~/.bashrc. Then every rm you type interactively asks first. We checked how it asks:
echo n | rm -i ifilerm: remove regular file 'ifile'?The answer n kept the file. Two limits: the alias exists only in your interactive shell, so scripts and sudo rm are not covered, and people who get used to typing y stop reading the question. Treat it as a speed bump, not a lock.
A trash bin for the command line. The trash-cli package (sudo apt install trash-cli) gives trash-put, which moves things to a trash directory instead of deleting them. We installed version 0.23.11.10:
trash-put pre2ls pre2ls: cannot access 'pre2': No such file or directorytrash-list2026-11-09 15:08:59 /tmp/rmlab/pre2The directory left its place but is recoverable with trash-restore (it is interactive and asks which item to restore) until you run trash-empty. Each disk partition has its own trash, so a file deleted from another partition is kept there. A trash bin does not free space; that is its price. On a server it suits interactive work on important data; it is the wrong tool for log rotation or build cleanup, where find -delete in a scheduled job is what you want.
For an empty one, rmdir folder. For a folder with files, rm -r folder. Run ls -la folder first so you know what goes with it.
rmdir works only on empty directories and refuses otherwise, so it cannot destroy data. rm -r deletes the directory and everything below it.
It deletes recursively (-r) and forces (-f): no confirmation for write-protected files and no error for missing paths. It is not "more powerful" than rm -r; it is quieter. It does not override permissions you do not have.
Use rm -r directory. If it fails with Permission denied, look at who owns the directory and its parent before adding sudo.
find folder -mindepth 1 -delete removes everything inside, including hidden files and subdirectories, and keeps folder (checked: only the empty folder was left). Run it first with -print instead of -delete.
Typical causes: it is most often a mount point (Device or resource busy), the file usually has the immutable attribute (Operation not permitted; sometimes +a or a sticky bit), or you were removing a different path than you thought, and -f hid the error. Re-run without -f and read the message.
find /path -type f -mtime +N -delete, after the same command with -print. Remove emptied directories afterwards with find /path -type d -empty -delete.
rmdir. Directory with contents: rm -r. -f only silences errors.ls -la, du -sh, find ... -print, echo rm ....find: filters first, -delete last, the same command with -print before it.rm -rf $VAR/ without ${VAR:?} or set -u; never put a trailing slash on a symlink.du, delete it, and check df: if the number did not drop, a process still holds the file.