How to Recover a Deleted File From a Backup Without Restoring Everything

How to Recover a Deleted File From a Backup Without Restoring Everything

To restore a single file from backup, find the file inside the archive or snapshot, read its metadata, and extract only that path to a scratch location. You never point the restore at the live document root. The target search phrase for this job is "restore single file from backup", and the whole trick is treating the backup as a read only filesystem you browse, not as something you unpack over your site.

Before you touch anything, stop writes to the affected directory if you can. A deleted file on a live filesystem is often still recoverable from the filesystem itself, and a running process or a log write can overwrite those blocks. If the file matters, put the site in maintenance mode or at least stop the process that owns the directory. Then work from a copy of the backup, never the only copy.

Know what kind of backup you are holding

The extraction method depends entirely on the container format, and people often guess wrong. A tar archive you unpack with tar. A gzipped tarball is the same with a decompression flag. A database dump is not a file archive at all and needs a database client. A filesystem snapshot is mounted or browsed with the snapshot tool. A block level image needs to be attached as a loop device before you can read anything out of it.

Identify the file first. The file command tells you the real type regardless of the extension, because a backup named site.tar.gz is frequently plain tar, or gzip with no tar inside.

$ file nightly-backup.tar.gz
nightly-backup.tar.gz: gzip compressed data, from Unix
$ tar -tzf nightly-backup.tar.gz | head -n 5
public_html/index.php
public_html/wp-config.php
public_html/wp-content/uploads/logo.png

The -t flag lists contents without extracting. The | head keeps the output short while you confirm the archive is what you think it is and that the path prefix matches the live layout. If listing takes a long time, the archive is large or on slow storage, and you should copy it to local disk before doing anything else.

Find the exact path inside the archive

Archives store paths, and the path inside the archive is what you extract, not the path on the live site. Grep the listing for the filename you want. Be careful with partial matches: config.php will also match wp-config.php and every backup copy of it. Use a pattern that anchors the filename.

$ tar -tzf nightly-backup.tar.gz | grep -E 'uploads/2024/logo\.png$'
public_html/wp-content/uploads/2024/logo.png

If the archive is a zip, use unzip -l to list and unzip with a single path to extract. If it is a database dump, you are not extracting a file from an archive at all, you are querying the dump, which is a different job and needs a running database or a parser.

For snapshots, the tool varies, but the principle holds: list the snapshot, then read the single path out. A filesystem snapshot exposed as a directory can simply be copied from, and a snapshot tool with a mount option lets you browse it like any other tree. Confirm the snapshot predates the deletion before you spend time on it.

Extract to a scratch directory, not the live tree

This is the step people get wrong. Extracting into the document root overwrites whatever is there now, including files that are newer and correct. Always extract to a temporary directory and copy the one file into place after you have looked at it.

$ mkdir -p /tmp/restore
$ tar -xzf nightly-backup.tar.gz -C /tmp/restore \
    public_html/wp-content/uploads/2024/logo.png
$ ls -l /tmp/restore/public_html/wp-content/uploads/2024/logo.png
-rw-r--r-- 1 deploy deploy 48210 Jan 12 03:14 /tmp/restore/public_html/wp-content/uploads/2024/logo.png

The -C flag changes directory before extracting, and the single path argument after it limits extraction to that member. The archive's directory structure is preserved under /tmp/restore, so you get the full relative path back. Check the size and modification time against what you expect. A zero byte file usually means the backup captured a failed write, and you want an older snapshot instead.

For a zip, the equivalent is unzip nightly-backup.zip public_html/wp-content/uploads/2024/logo.png -d /tmp/restore. For a snapshot directory, use cp -a from the snapshot path to your scratch directory. For a block image mounted read only, copy from the mount point. In every case the destination is scratch.

Compare before you copy over the live file

Once you have the file in scratch, diff it against the live version if one exists, or inspect it if it was deleted. For text files, diff shows you whether the backup is older or newer than what you have. For binary files, compare checksums. For an image or a PDF, open it. The point is to be sure you are restoring the right version and not an older copy that undoes a later fix.

$ diff /tmp/restore/public_html/config.php public_html/config.php
$ sha256sum /tmp/restore/public_html/uploads/logo.png \
    public_html/uploads/logo.png

If the live file is missing entirely, just verify the restored copy opens or parses. For PHP, php -l catches syntax errors before you put a broken file back. For a database dump fragment, run the statements against a scratch database first. When you are satisfied, copy the file into place with cp -a so ownership and timestamps survive, or move it and reset ownership to match the live tree.

Common failure modes

Permission and ownership problems are the most frequent surprise. Extraction as your own user produces files owned by you, which the web server may not be able to read. Extract as root only if you understand the risk, and fix ownership afterwards with chown to match the surrounding files. Symbolic links inside archives can also point outside the extraction directory, so check what you extracted before copying anything sensitive.

Another trap is restoring the wrong generation. Backups rotate, and the newest copy may already contain the corruption if the problem started before the backup ran. Walk back through older snapshots until you find one where the file is intact. Keep the archive read only during this process so you cannot accidentally modify the only good copy.

Finally, do not stop at the file. If the deletion was caused by a bad deploy, a script, or a compromised account, restoring the file alone leaves the cause in place and it will happen again. Note what changed around the deletion and address that separately.

Next, write down the exact archive path, the snapshot identifier, and the checksum of the file you restored, and keep that note with your incident record. Then test the restore path on a non critical file now, while nothing is broken, so the commands above are already familiar when you actually need them.

Related articles

Subscribe to our newsletter

Get the latest hosting tips, performance insights, and industry news.