How to prevent accidental rm -rf on Linux servers

Use layers, because each one covers a different slip. Make interactive rm ask with alias rm='rm -I --preserve-root=all', list paths that must never go in safe-rm, guard script variables with "${DIR:?}", make key files immutable with chattr +i, and move files to a trash instead of deleting them. None of these replaces snapshots and tested backups.

Updated . rm -I, --preserve-root=all, the aliases, zsh's rm * question, safe-rm 1.1 from Debian, set -u, ${VAR:?}, chattr +i, trash-cli 0.24 and recovery through /proc and lsof were run on Debian 13 containers with GNU coreutils 9.7, bash 5.2 and zsh 5.9.

What rm already refuses, and what it does not

GNU rm refuses rm -rf / by default. That protects one literal path. The common accidents look different:

  • rm -rf /*: the shell expands the star first, so rm gets /bin, /etc and the rest, and none of them is /.
  • rm -rf /var/app /*: one stray space adds the whole system to the list.
  • An empty variable in a script: rm -rf "$DIR"/* becomes rm -rf /*.
  • The right command in the wrong terminal, on a server you thought was staging.

--preserve-root=all goes a step further and refuses any folder that sits on a different disk from its parent, such as a mounted volume:

rm: skipping '/dev/shm', since it's on a different device
rm: and --preserve-root=all is in effect

Habits like listing a glob with echo before deleting it are in how to avoid mistakes on production servers.

Make interactive rm ask once

Put these two lines in ~/.bashrc on the server, and in /root/.bashrc:

alias rm='rm -I --preserve-root=all'
alias sudo='sudo '

-I asks once before a recursive delete or before removing more than three files. Anything but y removes nothing. The trailing space in the sudo alias tells bash to expand the next word as an alias too, so sudo rm asks as well:

$ sudo rm /tmp/s/*
rm: remove 4 arguments? n

zsh asks by itself before rm * and rm path/*, unless RM_STAR_SILENT is set:

zsh: sure you want to delete all 2 files in /tmp/z [yn]? n

Aliases apply only to interactive shells. Scripts, cron jobs and ssh server 'rm ...' use the real rm, and \rm or command rm skip the alias on purpose. Avoid alias rm='rm -i': a question for every file trains people to type -f.

safe-rm: paths that rm will skip

safe-rm puts a wrapper in front of rm that skips any path on a protected list. On Debian and Ubuntu:

sudo apt install safe-rm
echo '/srv/app/shared' | sudo tee -a /etc/safe-rm.conf

The list already includes system folders such as /etc, /usr and /var. Our test:

$ rm -rf /srv/app/shared
safe-rm: Skipping /srv/app/shared.

Know its limits before you rely on it:

  • It protects the listed path, not what is inside. rm -rf /srv/app/shared/* still emptied the folder. A line such as /srv/data/* protects each entry directly under /srv/data.
  • It exits with 0 even when it skipped something, so a script does not notice.
  • The package adds its folder to PATH in /etc/profile.d, so it works in login shells and with sudo -i. sudo rm uses sudo's own secure_path and ran the real rm in our test, and so does ssh server 'rm ...'.

Guard the variables in scripts

Most scripted disasters come from an empty or unset variable. Stop the script before it reaches rm:

#!/usr/bin/env bash
set -euo pipefail
rm -rf -- "${BUILD_DIR:?}/"
s.sh: line 3: BUILD_DIR: parameter null or not set

${BUILD_DIR:?} stops with an error when the variable is empty or missing, and set -u does the same for every unset variable in the script. -- ends the options, so a file named -rf is treated as a file.

Permissions and immutable files

The strongest guard is not being able to delete. Work as a normal user and use sudo only for the commands that need it, so a slip in your home folder cannot reach the system. See how to create an SSH user with sudo.

For files that must not change even for root, set the immutable flag, on ext4 and other filesystems that support it:

sudo chattr +i /etc/app/config.yml
lsattr /etc/app/config.yml
----i---------e------- /etc/app/config.yml
rm: cannot remove '/etc/app/config.yml': Operation not permitted

The same error stopped rm -rf on the folder around it. Remove the flag with sudo chattr -i before a planned change. Updates and editors cannot write to the file either, so keep the flag for the files that really need it.

A trash instead of rm

When you clean up by hand, move files to a trash instead. trash-cli uses the same trash as desktop Linux:

sudo apt install trash-cli
trash-put old-release/
trash-list
trash-restore
trash-empty 30

trash-restore asks which item to put back. trash-empty 30 removes what has been in the trash for more than 30 days, which suits a cron job. The files still use disk space until then. Do not alias rm to trash-put: the options differ, and the habit fails on servers without it.

Snapshots, backups, and if it already happened

Every layer above can be bypassed. A snapshot cannot be deleted with rm. Take one before risky work: zfs snapshot pool/data@before-cleanup on ZFS, btrfs subvolume snapshot -r on btrfs, lvcreate --snapshot on LVM, or a volume snapshot in your cloud console. Copy backups to another machine, for example with rsync over SSH, and test a restore now and then.

If files are already gone, stop writing to that disk: new data can overwrite what a recovery tool might still find. Then restore from a snapshot or backup. One case can be saved at once: a file still open in a running program. Find it and copy it back before that program stops:

lsof +L1
cp /proc/374/fd/3 /root/data.recovered
COMMAND PID USER FD   TYPE DEVICE SIZE/OFF NLINK     NODE NAME
tail    374 root 3r   REG  0,186        2     0 12873355 /srv/data.txt (deleted)

The number after /proc/ is the PID, and the one after fd/ is the number in the FD column without its letter.

Questions

Does rm -rf / still work on Linux?

Not with GNU rm: it refuses / unless you add --no-preserve-root. But rm -rf /* is expanded by the shell into /bin, /etc and the rest, and that is not refused.

Can I undo rm -rf?

Not with rm itself. Restore from a snapshot or backup. A file that a running program still has open can be copied back from /proc/PID/fd until that program closes it.

Is alias rm='rm -i' a good idea?

It asks for every file, so people learn to answer yes without reading or to add -f. rm -I asks once, for three or more files or for a recursive delete, which keeps the question meaningful.

Does safe-rm protect the files inside a folder?

No. It skips the paths listed in its configuration. rm -rf /srv/app/shared/* still empties a protected /srv/app/shared, unless the entries inside are listed too, for example with /srv/app/shared/*.

Sources

More in Securing a server

All guides