Home › The Stash

Linux for Defenders 13: Disks, fstab and a Backup You Have Actually Restored

LabOctober 23, 2026

Beginner to intermediateTestedTime ~40 minTool lsblk, fstab, findmnt, tar, rsyncNeeds a Linux VM you ownCost freeExam Linux+ · Security+

0 / 8 steps

The goal

By the end you will have mounted a disk the way a hardened server does (by UUID, with nodev,nosuid,noexec), caught an fstab typo before it could break a boot, taken a backup with checksums, lost the files on purpose, and restored them and proved every byte came back. You will also see why an rsync --delete mirror is not a backup, and how to keep dated copies without filling the disk.

Your own VM only. Use a Linux VM you own and can roll back (the target VM from Lab 00, or any throwaway Ubuntu), with a snapshot first. Everything happens on a new practice disk and in /srv/data and /backup; step 1 is the only place to be careful, so you format the practice disk and not the system disk.

Why this lab. Two things go wrong on real servers more often than any attack: a disk that does not mount after a reboot, and a backup that turns out not to restore. Both are cheap to prevent and expensive to discover. Linux+ covers mounting and fstab, Security+ covers backups and recovery, and this lab does both with a drill you can repeat.

Nova's backup plan has checksums, a restore drill and a retention rule. Mine is to watch her restore it. — Nyx

Before you start. You need the Ubuntu VM and the alex user from Lab 01 (step 6 makes alex the owner of one file, to prove owners come back). Run the steps as a user with sudo. The Red Hat way is at the end. Every output below is real, from the lab machine.

Steps

01Add a practice disk

You need a spare disk to format and mount without risking the system disk. The cleanest way is to add a 1 GB virtual disk to the VM in VMware or VirtualBox (it shows up as /dev/sdb). If you can't, make a file that behaves like a disk. The outputs below use the file method:

$ sudo truncate -s 1G /srv/lab-disk.img
$ sudo losetup --find --show /srv/lab-disk.img
/dev/loop0
$ lsblk -o NAME,SIZE,TYPE,FSTYPE,MOUNTPOINTS /dev/loop0
NAME  SIZE TYPE FSTYPE MOUNTPOINTS
loop0   1G loop

losetup attaches the file as a block device and prints its name. On Ubuntu, snaps already use several loop devices, so yours may well be /dev/loop7 or similar: use the name it printed everywhere this lab says /dev/loop0. If you added a real virtual disk instead, use its name (/dev/sdb, /dev/vdb or /dev/nvme0n2 depending on the hypervisor), and check the name with lsblk first: formatting the wrong disk is the one mistake in this lab you cannot undo.

Why this matters. Every storage task starts with "which disk is which?" lsblk answers it with sizes and mount points, so you never guess from a device name.

02Format it and find its UUID

Create a filesystem with a label, then ask for its identity:

$ sudo mkfs.ext4 -q -L labdata /dev/loop0
$ sudo blkid /dev/loop0
/dev/loop0: LABEL="labdata" UUID="e0565f4f-dff4-4c54-b825-7e061318cb61" BLOCK_SIZE="4096" TYPE="ext4"

The UUID is the filesystem's permanent ID; yours will be different. Device names like /dev/sdb can change between boots when disks are added or reordered. The UUID does not, which is why /etc/fstab should always use it.

Why this matters. "The server came up with the data disk mounted in the wrong place" is almost always an fstab that used /dev/sdX names. UUIDs prevent it.

03Mount it at boot, locked down

Back up /etc/fstab, then add one line for the new disk, using your own UUID from step 2:

$ sudo cp /etc/fstab /etc/fstab.bak
$ sudo mkdir -p /srv/data
$ sudo nano /etc/fstab

Add this as the last line:

UUID=e0565f4f-dff4-4c54-b825-7e061318cb61  /srv/data  ext4  defaults,nofail,nodev,nosuid,noexec  0  2

The columns are: what to mount, where, filesystem type, options, a legacy dump flag (0), and the check order at boot (2 = after the root disk). The options are the security part: nodev (no device files), nosuid (set-UID programs lose their power here), noexec (programs on this disk cannot be started directly). nofail tells systemd not to stop the boot if this disk is missing, which matters for the file method because the loop disk is not set up again after a reboot. Now check the file before anything depends on it:

$ sudo systemctl daemon-reload
$ sudo findmnt --verify
Success, no errors or warnings detected
$ sudo mount -a
$ findmnt /srv/data
TARGET    SOURCE     FSTYPE OPTIONS
/srv/data /dev/loop0 ext4   rw,nosuid,nodev,noexec,relatime

Why this matters. A data disk holds files users upload or copy in. There is no reason anything on it should run as a program or act as a device, so these three options remove whole classes of abuse for free. CIS benchmarks require them on /tmp, /var/tmp and /dev/shm for the same reason.

04Catch a typo before it breaks a boot

Here is the mistake that sends a server to the emergency console after a reboot (for a line without nofail; with nofail the boot waits about 90 seconds, then carries on with the disk silently not mounted, and anything written to /srv/data lands on the root disk instead). Make a copy of fstab with one wrong character in the UUID and check it:

$ sed 's/e0565f4f/e0565f4e/' /etc/fstab > /tmp/fstab.typo
$ sudo findmnt --verify --tab-file /tmp/fstab.typo

0 parse errors, 1 error, 0 warnings
/srv/data
   [E] unreachable on boot required source: UUID=e0565f4e-dff4-4c54-b825-7e061318cb61

Use your own first eight UUID characters in the sed line and change the last one. Run findmnt --verify with sudo: without it, it cannot read the disks and adds "Permission denied" warnings. findmnt --verify caught it: no disk with that UUID exists. Make it a habit to run it after every fstab edit, and before every reboot. If you ever forget systemctl daemon-reload, it also warns that systemd is still using the old version of the file.

Why this matters. A bad fstab line is one of the most common self-inflicted outages on Linux, and on a cloud server with no console it can mean rebuilding the machine. One command prevents it.

05See what noexec does, and what it doesn't

Put a tiny script on the disk and try to run it two ways:

$ printf '#!/bin/sh\necho hello from data\n' | sudo tee /srv/data/hello.sh > /dev/null
$ sudo chmod +x /srv/data/hello.sh
$ sudo /srv/data/hello.sh
sudo: unable to execute /srv/data/hello.sh: Permission denied
$ sh /srv/data/hello.sh
hello from data

Running it directly fails, even as root, because the mount says noexec. Handing it to an interpreter (sh, bash, python3) works, because then the program being started is /usr/bin/sh, which lives on a normal disk.

Why this matters. noexec stops drive-by execution (a downloaded binary, a malicious upload run by path), not a determined attacker. Write it in a hardening report as "reduces", never as "prevents".

06Take a backup you can trust

Put some realistic files on the disk, record a checksum of every file, then archive the whole folder (tar records owners and permissions; they come back when root extracts it):

$ sudo mkdir -p /srv/data/reports /srv/data/config
$ for m in jan feb mar; do echo "quarterly numbers for $m" | sudo tee /srv/data/reports/$m.csv > /dev/null; done
$ echo "db_host=127.0.0.1" | sudo tee /srv/data/config/app.conf > /dev/null
$ sudo chmod 600 /srv/data/config/app.conf
$ sudo chown alex:alex /srv/data/reports/mar.csv
$ sudo ln -s reports/mar.csv /srv/data/latest.csv
$ cd /srv/data && sudo find . -type f -exec sha256sum {} + | sort -k2 | sudo tee /root/data-manifest.sha256 > /dev/null
$ sudo mkdir -p /backup
$ sudo tar --exclude=lost+found -cpzf /backup/data-2026-10-23.tar.gz -C /srv data
$ ls -l /backup/
total 4
-rw-r--r-- 1 root root 385 Oct 23 13:33 data-2026-10-23.tar.gz

Look at those permissions: -rw-r--r-- means every user on the machine can read the archive, including config/app.conf, which was 600 (owner only) on the disk. A backup quietly undoes the file permissions you set. Fix it (the file first: once /backup is 700, your own shell can no longer list it, so a * in the path would fail), and record a checksum of the archive itself:

$ sudo chmod 600 /backup/data-2026-10-23.tar.gz
$ sudo chmod 700 /backup
$ sudo sh -c 'sha256sum /backup/data-2026-10-23.tar.gz > /backup/data-2026-10-23.tar.gz.sha256'

tar option order. Put --exclude before the folder names. Written after them (... -C /srv data --exclude=lost+found), GNU tar 1.35 refuses: "The following options were used after non-option arguments", and exits with an error.

Why this matters. Backups are a favourite target exactly because they collect everything in one readable file. An attacker who cannot read app.conf may well be able to read last night's archive.

07The drill: lose the files, then restore them

Simulate the bad day. This scrambles the three reports into .locked files and deletes the originals, the way ransomware leaves a folder:

$ cd /srv/data/reports && for f in *.csv; do sudo sh -c "base64 $f > $f.locked && rm $f"; done
$ ls /srv/data/reports
feb.csv.locked
jan.csv.locked
mar.csv.locked
$ cat /srv/data/latest.csv
cat: /srv/data/latest.csv: No such file or directory

Now restore, in the right order: check the archive is intact, restore it somewhere safe first, prove every file is right, and only then put it back:

$ sudo sha256sum -c /backup/data-2026-10-23.tar.gz.sha256
/backup/data-2026-10-23.tar.gz: OK
$ sudo mkdir -p /root/restore-test && sudo tar -xpzf /backup/data-2026-10-23.tar.gz -C /root/restore-test
$ sudo sh -c 'cd /root/restore-test/data && sha256sum -c /root/data-manifest.sha256'
./config/app.conf: OK
./hello.sh: OK
./reports/feb.csv: OK
./reports/jan.csv: OK
./reports/mar.csv: OK
$ sudo rsync -a --dry-run --itemize-changes /root/restore-test/data/reports/ /srv/data/reports/
>f+++++++++ feb.csv
>f+++++++++ jan.csv
>f+++++++++ mar.csv
$ sudo rsync -a /root/restore-test/data/reports/ /srv/data/reports/
$ sudo rm /srv/data/reports/*.locked
$ cat /srv/data/latest.csv
quarterly numbers for mar
$ ls -l /srv/data/reports
total 12
-rw-r--r-- 1 root root 26 Oct 23 13:33 feb.csv
-rw-r--r-- 1 root root 26 Oct 23 13:33 jan.csv
-rw-r--r-- 1 alex alex 26 Oct 23 13:33 mar.csv

Every checksum matches, mar.csv is owned by alex again (when root extracts, tar restores owners by default; -p on extract restores the exact permissions), and the latest.csv shortcut works because its target is back. The --dry-run --itemize-changes line showed exactly what rsync was about to write before it wrote anything (>f+++++++++ = new file).

Why this matters. A backup that has never been restored is a hope, not a backup. /root is readable only by root, which is why the check runs inside sudo sh -c. Restoring to a test folder first means a bad archive can never overwrite what little you still have.

08Why a mirror is not a backup, and keeping old copies

Many people "back up" with rsync --delete to a second disk. Watch what happens to a mistake:

$ sudo rsync -a --delete /srv/data/ /backup/mirror/
$ sudo rm /srv/data/reports/jan.csv
$ sudo rsync -a --delete --dry-run --itemize-changes /srv/data/ /backup/mirror/
*deleting   reports/jan.csv
.d..t...... reports/
$ sudo rsync -a --delete /srv/data/ /backup/mirror/
$ sudo ls /backup/mirror/reports
feb.csv
mar.csv

The mirror faithfully copied the mistake: jan.csv is gone from both. The dated archive still has it, and you can pull back a single file:

$ sudo tar -tzf /backup/data-2026-10-23.tar.gz data/reports/jan.csv
data/reports/jan.csv
$ sudo tar -xpzf /backup/data-2026-10-23.tar.gz -C /srv data/reports/jan.csv
$ ls /srv/data/reports
feb.csv
jan.csv
mar.csv

Dated archives need a retention rule, or the backup disk fills. Preview which ones are old before deleting anything (the lab box had copies dated 10, 14 and 20 October). -mtime +7 means more than seven whole days since the file was last modified, so in practice 8 days or older; it reads the file's own timestamp, not the date in its name, and copying an archive without cp -p resets that timestamp:

$ sudo find /backup -maxdepth 1 -name 'data-*.tar.gz' -mtime +7 -print
/backup/data-2026-10-14.tar.gz
/backup/data-2026-10-10.tar.gz

When the list is right, the same command with -delete instead of -print removes them. To finish, undo the lab. Leave the folder first (cd ~), or umount says "target is busy". Then sudo umount /srv/data, sudo cp /etc/fstab.bak /etc/fstab, sudo systemctl daemon-reload, and for the file method sudo losetup -d /dev/loop0 (your loop name) and sudo rm /srv/lab-disk.img. Delete the lab copies too: sudo rm -r /backup /root/restore-test /root/data-manifest.sha256 /etc/fstab.bak.

Why this matters. The 3-2-1 rule (three copies, two kinds of storage, one off the machine) exists because mirrors copy deletions and ransomware instantly. Versioned, dated copies are what let you go back to before the mistake.

What you learned

  • lsblk and blkid — which disk is which, and its permanent UUID
  • /etc/fstab by UUID — device names can change between boots; UUIDs do not
  • nodev,nosuid,noexec — free hardening for data disks; noexec reduces, it does not prevent (sh file still runs)
  • findmnt --verify — catches a bad fstab line before the reboot does; nofail keeps a missing disk from stopping the boot
  • tar -czf / tar -xpzf as root + sha256sum — owners and permissions come back on restore, and a checksum proves the archive is intact
  • Backup permissions — an archive can expose files that were locked down on disk; chmod 600 it
  • The restore drill — verify the archive, restore to a test folder, check every file, then put back
  • Mirror vs versions — rsync --delete copies mistakes; dated archives plus find -mtime +7 retention do not

On Red Hat (RHEL, Rocky, Fedora) and Debian. Checked on Rocky Linux 9.8: RHEL's default filesystem is XFS, so step 2 is sudo mkfs.xfs -L labdata /dev/loop0 (install xfsprogs if missing) and the fstab line says xfs instead of ext4; the same nodev,nosuid,noexec options apply and show up in findmnt. tar, rsync (sudo dnf install rsync on minimal installs), sha256sum, losetup and findmnt --verify work the same (util-linux 2.37 on RHEL 9 vs 2.39 on Ubuntu 24.04). Debian 12 matches Ubuntu. LVM (volume groups, resizing, snapshots) is the natural next step and gets its own lab.

Put it on your resume. Configured Linux storage with UUID-based fstab entries and nodev/nosuid/noexec hardening, validated changes with findmnt --verify, and ran a backup-and-restore drill with tar, rsync and SHA-256 manifests, including permission-safe archives and dated retention. Change it to match what you actually did.

Tried it? Tell us

Nova built this lab from Steven’s ideas, and every command above was run on a real machine before it went up. If a step behaved differently for you, or you know a better way, tell us. Ideas for the next lab are welcome too — no promises on timing.

Next90 Days of Cybersecurity — where this series fits→

Go deeper

Original walkthrough. Every command and output above was run on Ubuntu 24.04 (util-linux 2.39.3, GNU tar 1.35, rsync 3.2.7) with a file-backed loop disk, and the Red Hat differences checked on Rocky Linux 9.8. If something does not match your machine, tell me and I’ll fix it.