VPS Snaps

How to recover deleted files on Linux

Linux has no undo for rm: it removes the file's name and marks its space free for reuse. The reliable way back is a backup or snapshot. Without one, check whether a program still has the file open, so you can copy it out of /proc; otherwise stop writing to that filesystem and run recovery tools on a copy of the disk, knowing they often fail on ext4 and find nothing once the disk has been trimmed.

10 min readUpdated Tested on Ubuntu 24.04 LTS, lsof 4.95.0, GNU tar 1.35, util-linux 2.39.3

The honest answer: restore it from a backup

GNU rm's manual says only that it might be possible to recover some of a removed file's contents, given sufficient expertise and time. On ext4 the odds are poor: ext4 maintainer Theodore Ts'o wrote in 2021 that ever since ext3, unlinking a file zeroes the inode's block pointers and extent tree, so the filesystem forgets where the data was. Since Linux 5.13, ext4 also wipes the directory entry, so the name goes too. Tools have to guess, from old copies in the journal or by recognizing file contents.

SituationWhat to doChances
A program still has the file openCopy it from /procGood, while that program runs
A backup or snapshot from before the deletionRestore just that fileGood
ext3 or ext4, deleted minutes ago, no backupImage the disk; try extundelete, then PhotoRecPossible, often partial
XFS or Btrfs, no backupImage the disk; PhotoRec, or btrfs restorePossible, often partial
Disk mounted with discard, or fstrim has run sinceRestore from a backupEffectively none

First, stop the damage

Any write to that filesystem can land on the freed blocks: logs, temp files, installing the recovery tool. In order:

  1. Check for open copies before stopping anything (next section). Stopping or restarting the program that holds a deleted file open frees it for good.
  2. Stop the services that write to that filesystem.
  3. If it is a separate filesystem such as /data, remount it read-only; if the mount is busy, stop whatever still writes there and retry.
  4. If it is the root filesystem, shut the server down and attach its disk to another server, or boot your provider's rescue system.
  5. Image it to a different disk and work on the image; extundelete and PhotoRec both accept an image file. bs=4M copies 4 MB at a time instead of dd's 512-byte default, and status=progress shows progress.
Terminal
sudo mount -o remount,ro /data
Terminal
sudo dd if=/dev/vdb1 of=/mnt/rescue/vdb1.img bs=4M status=progress

These two and the recovery tools below need a real disk, so they were checked against their manuals rather than run on our test server. Never recover files to the filesystem you are recovering from.

If a program still has it open, copy it from /proc

A deleted file's data stays on disk until the last program holding it open closes it: a log deleted while the web server writes to it, a SQLite database deleted under a running app. +L1 lists open files whose link count is below 1, meaning they have no name left:

Terminal
sudo lsof +L1

In our test, tail -f stood in for a daemon, holding open an orders.csv we then deleted:

Output (test directory shortened)
COMMAND       PID USER   FD   TYPE DEVICE SIZE/OFF NLINK    NODE NAME
tail      1776325 root    3r   REG  253,1       32     0 2658784 /tmp/…/orders.csv (deleted)

The PID is 1776325 and 3r is file descriptor 3, open for reading. lsof -a -p 1776325 +L1 narrows the list to one process (-a ANDs the conditions), and +aL1 /data to one filesystem. Without lsof, ls -l /proc/1776325/fd shows the same link: 3 -> …/orders.csv (deleted). Reading that link reads the deleted file:

Terminal
sudo cp /proc/1776325/fd/3 orders.csv.recovered

The copy had all 32 bytes. You cannot give the original its name back: ln on the /proc link failed with Invalid cross-device link, and ln -L with No such file or directory.

  • Copy before you stop or restart the program; after our test killed tail, its /proc entry was gone, and so was the file.
  • A file still being written, such as a log, is copied as it is at that moment; let a database check a copied database file.
  • Reading another user's /proc links needs root or that user, under the kernel's ptrace access check.

Check whether TRIM has already erased it

SSDs and thin-provisioned virtual disks accept discard (TRIM) requests: the filesystem tells the disk which blocks are free, and the disk may unmap or erase them. util-linux's blkdiscard manual is blunt: all data in the discarded region is lost. With the discard mount option, ext4 sends discards as soon as blocks are freed; without it, fstrim trims all free space at once, weekly from Ubuntu's fstrim.timer. Check the mount options:

Terminal
findmnt -o TARGET,SOURCE,FSTYPE,OPTIONS -T /home
Output
TARGET SOURCE    FSTYPE OPTIONS
/      /dev/vda1 ext4   rw,relatime,discard,errors=remount-ro,commit=30

On the Ubuntu 24.04 cloud server we tested on, the root filesystem is mounted with discard, so a deleted file's blocks are discarded at once. The weekly trim:

Terminal
systemctl list-timers fstrim.timer
Output
NEXT                        LEFT LAST                            PASSED UNIT         ACTIVATES
Mon 2026-10-05 01:07:53 UTC   8h Mon 2026-09-28 00:12:11 UTC 6 days ago fstrim.timer fstrim.service

1 timers listed.
Pass --all to see loaded but inactive timers, too.

And whether the disk accepts discards at all:

Terminal
lsblk --discard
Output
NAME    DISC-ALN DISC-GRAN DISC-MAX DISC-ZERO
vda            0      512B       2G         0
├─vda1         0      512B       2G         0
├─vda14        0      512B       2G         0
├─vda15        0      512B       2G         0
└─vda16        0      512B       2G         0
vdb            0      512B       2G         0

Non-zero DISC-GRAN and DISC-MAX mean the disk accepts discards; the kernel documents a maximum of 0 as no support. Don't read anything into DISC-ZERO: the kernel now always reports 0 there and says not to rely on any particular behavior after a discard. If the filesystem has discard, or the timer has run since the deletion, go straight to your backups.

ext4: debugfs, extundelete and PhotoRec

Run these against the image or an unmounted filesystem, with their output on another disk.

debugfs comes with e2fsprogs and opens a filesystem, or an image file, read-only unless you pass -w. Its lsdel request lists deleted inodes, the classic ext2 undelete. On ext4 it rarely helps, because the block maps it needs are already zeroed; Ts'o judged that almost nobody had used the lsdel-and-link approach for years.

Terminal
sudo debugfs -R lsdel /mnt/rescue/vdb1.img

extundelete searches the journal for an older copy of the deleted file's inode, which still says where the data was. It works on ext3, and on ext4 with a journal, unmounted or read-only (or an image), and writes what it finds to RECOVERED_FILES in the current directory, so change to another disk first. Its last release, 0.2.4, came out in January 2013; treat it as unmaintained, though Debian and Ubuntu still package it. The path is relative to the filesystem's root:

Terminal
cd /mnt/rescue
Terminal
sudo extundelete vdb1.img --restore-file var/www/shop/uploads/orders.csv

--restore-all tries everything, and --after takes a time in seconds since 1970 (date -d '2026-10-04 09:00' +%s). Its own instructions stress working quickly until the filesystem is unmounted, because background writes can overwrite what you are trying to save.

PhotoRec is an open-source (GPL) tool from CGSecurity, shipped in the testdisk package. It ignores the filesystem and finds files by recognizing their contents, so it works on any filesystem, but names, dates and folders are lost, and what it finds lands in numbered folders: recup_dir.1, recup_dir.2 and so on. It is interactive; choose the ext2/ext3/ext4 option when it asks for the filesystem type.

Terminal
sudo photorec /log /d /mnt/rescue/recup_dir /mnt/rescue/vdb1.img

TestDisk, from the same package, finds lost partitions and repairs partition tables. Its undelete covers FAT, exFAT, NTFS and ext2, not ext3 or ext4, so on a Linux server it is the tool for a deleted partition, not a deleted file.

XFS and Btrfs

XFS has no undelete command: xfsprogs 6.6 on Ubuntu 24.04 ships repair, debugging and copy tools, none for deleted files. The third-party open-source xfs_undelete, not packaged by Debian or Ubuntu, scans for deleted inodes. Its README says file names and paths cannot be recovered, files in more than about 21 extents cannot be recovered, and most files come back padded with zeros. Otherwise, use PhotoRec on an image.

Btrfs has no undelete either. btrfs restore copies files out of a damaged filesystem into another directory without modifying it, and -D lists what it would copy. Its -t option reads an older root tree, whose address btrfs-find-root can find, and an older tree may still point to a file deleted shortly before. The docs present it as a salvage tool, not an undelete, so treat a result as luck:

Terminal
sudo btrfs restore -D /dev/vdb1 /mnt/rescue/btrfs-out

The real Btrfs undo is a read-only snapshot taken before the mistake. The Btrfs docs add that a snapshot is not a backup: it shares its data blocks with the original, so damage to those blocks hits both.

Files deleted inside Docker volumes

A named Docker volume is a directory on the host, by default /var/lib/docker/volumes/<name>/_data, so everything above applies to the host's disk. Two differences:

  • Containers' processes are host processes. A process is visible from every ancestor PID namespace, under a host PID. Run lsof +L1 on the host rather than inside the container, and copy from the host's /proc.
  • Whole volumes are easy to delete. docker compose down -v removes the named volumes declared in the compose file and anonymous volumes, and docker volume prune --all removes every volume no container uses (without --all, only anonymous ones). Either is an rm -rf of the data.

Back volumes up with a throwaway container, as in Docker volume backups.

Restore one file from a backup

Restore into a staging directory, check the file, then copy it into place. From a tar archive, find the file's exact name first:

Terminal
tar -tzf www-2026-10-03.tar.gz | grep orders
Output
var/www/shop/uploads/orders.csv
Terminal
mkdir -p restore
Terminal
tar -xzf www-2026-10-03.tar.gz -C restore var/www/shop/uploads/orders.csv

That extracted only restore/var/www/shop/uploads/orders.csv. Use the name exactly as listed: GNU tar strips the leading / when it creates an archive, and asking for /var/www/shop/uploads/orders.csv failed with tar: /var/www/shop/uploads/orders.csv: Not found in archive (exit status 2). -O prints a member instead of writing it:

Terminal
tar -xzf www-2026-10-03.tar.gz -O var/www/shop/uploads/orders.csv
  • tar, more: wildcards and ownership on extract are in the tar guide. restic: restore with --include and a --target directory brings back one path (restic guide).
  • Cloud snapshot: on AWS, make a volume from the snapshot, attach it and mount it read-only (EBS snapshots). DigitalOcean, Hetzner and Vultr snapshots restore as whole servers: create a temporary one, copy the file across, then delete it (DigitalOcean snapshots).
  • LVM or ZFS snapshot: mount the LVM snapshot read-only, or copy from ZFS's .zfs/snapshot directory (LVM, ZFS).

Make the next one easy to undo

  • Backups kept long enough, because some deletions go unnoticed for weeks (retention), and restored now and then (restore drill).
  • Frequent snapshots where they are cheap: LVM, ZFS, Btrfs read-only snapshots, or your provider's.
  • A prompt on big deletions. With the alias below, rm asks once before removing more than three files or anything recursively. In our test it asked rm: remove 4 arguments? and rm: remove 1 argument recursively?; \rm skips the alias.
  • A trash can for people. The trash-cli package in Debian and Ubuntu moves files to a trash directory with their original path and deletion date, and its trash-restore puts them back. The safe-rm package refuses to delete paths listed in /etc/safe-rm.conf.
~/.bashrc
alias rm='rm -I'

Aliases only work in interactive shells: bash does not expand them in scripts or cron jobs. Scripts need their own guards, such as the prune script in the find guide, and backups remain the real answer.

Frequently asked questions

Can I undo rm on Linux?
No. rm removes the file's name and frees its space. Restore it from a backup or snapshot. If a program still has it open, copy it from /proc/<pid>/fd. Otherwise, recovery tools on a copy of the disk are a best effort.
Where do deleted files go on Linux?
Nowhere. rm doesn't use a trash folder; desktop file managers do. The trash-cli package gives servers the same kind of trash, for files you delete with it.
Can deleted files be recovered from an SSD?
Usually not once the space has been trimmed. If the filesystem is mounted with discard, that happens at deletion; otherwise fstrim does it, typically weekly. util-linux's manual says data in a discarded region is lost.
Does extundelete still work on ext4?
Sometimes, for recent deletions on a filesystem with a journal, because it looks for old copies of the file's inode there. Its last release was in January 2013. Run it on an image, never on the live disk.
How do I recover a deleted file that is still open?
Run sudo lsof +L1 to find the process ID and file descriptor, then copy /proc/<pid>/fd/<fd> to a new file before that process stops.

How this was checked

The commands were run on Ubuntu 24.04 LTS, lsof 4.95.0, GNU tar 1.35, util-linux 2.39.3 on October 4, 2026. Any that need something this test server does not have, such as a second server, a cloud account or another database engine, were checked against the official pages below instead.

Sources, on October 4, 2026: