Stop Backups Writing to an Unmounted Disk

Stop Backups Writing to an Unmounted Disk

Prevent Linux backup jobs from filling your root disk when a backup drive is missing. Add mount checks, systemd dependencies, and a safe failure-path test.

💡 Disclosure: This article contains affiliate links. If you make a purchase through these links, we may earn a small commission at no extra cost to you. This helps support the site and keeps the content free.

Your backup disk disappears. The backup job still runs. By morning, the root filesystem is full, and the backup directory looks suspiciously healthy.

This is the trap with Linux mount points: /mnt/backup is still a directory when nothing is mounted there. A script that checks only whether that directory exists can happily write onto the server’s root disk instead.

I would rather have a loud failed backup than a successful copy to the wrong filesystem. This guide adds a mount guard to a Linux backup job, connects it to systemd, and tests the missing-disk case without unplugging production storage.

The short version: declare your real mount, verify that the destination is a mount point, and make failures visible. A directory existing is not proof that a backup drive is present.

Understand the failure before changing anything

Imagine a USB backup drive normally mounted at /mnt/backup. While mounted, files written below that path land on the drive. After an unmount, the underlying directory becomes visible again, and writes land on the filesystem containing that directory.

The path has not changed. The destination has.

This also affects NAS mounts and attached VPS volumes. A scheduled export can fill local storage while the storage you intended to use remains offline. The resulting disk-pressure symptoms can look like the problems in our Linux disk-space troubleshooting guide, but deleting files does not fix the missing mount.

Inspect the destination before touching it:

findmnt --mountpoint /mnt/backup
findmnt --target /mnt/backup
mountpoint -- /mnt/backup

findmnt --mountpoint asks about that exact mount point. findmnt --target reports the filesystem containing a path, so it can report your root filesystem even when the backup drive is absent. That distinction is the whole problem.

These examples assume a conventional Linux host with util-linux tools and systemd. Check command paths with command -v before copying them into a service unit.

First, make the mount explicit

For a local block device, identify the filesystem deliberately:

lsblk -f
sudo blkid

Match the expected drive, filesystem, and UUID. Do not identify a backup disk solely by a name such as /dev/sdb1; device enumeration can change after a reboot.

If you already have a working /etc/fstab entry, keep it and inspect it rather than adding another one. For a new local ext4 backup filesystem, an illustrative entry is:

UUID=REPLACE_WITH_YOUR_BACKUP_FILESYSTEM_UUID /mnt/backup ext4 defaults,nofail 0 2

This is a template, not a command to run unchanged. Use the actual filesystem type and options appropriate to your device. NFS and CIFS need different configuration, credentials, and network considerations.

Create the mount-point directory if necessary, validate the file, and mount just this destination:

sudo mkdir -p /mnt/backup
sudo findmnt --verify --verbose
sudo systemctl daemon-reload
sudo mount /mnt/backup
findmnt --mountpoint /mnt/backup

Stop if validation or mounting fails. nofail lets boot continue without this disk; it does not make backup writes safe. We add that protection next.

Put the guard inside the backup script

A systemd dependency helps scheduled runs. A script-level guard also protects a manual invocation, which is why I prefer using both.

Put this near the top of your existing Bash backup script, before creating directories or opening output files:

#!/bin/bash
set -euo pipefail

backup_mount=/mnt/backup

if ! mountpoint -q -- "$backup_mount"; then
  printf 'ERROR: backup destination is not mounted: %s\n' \
    "$backup_mount" >&2
  exit 1
fi

# Only now run the existing backup command.
# Keep all destination paths below "$backup_mount".

Keep the script root-owned and not writable by the account that merely schedules it. Avoid adding a destructive cleanup operation here. A missing destination should stop the job, not trigger attempts to repair storage automatically.

mountpoint proves that something is mounted there, not that it is the correct disk. If using the wrong mounted filesystem is a realistic risk, compare the expected UUID or source using findmnt, and fail on a mismatch. Document the expected identity outside the backup disk so you can still check it when the disk is unavailable.

Nor is this an atomic guarantee. A mount can fail after the check, and a filesystem can become read-only or return I/O errors during a write. Keep the backup tool’s exit status and verify completed backups independently.

Make systemd wait for storage

Suppose your existing backup service is named homelab-backup.service. Create a drop-in using:

sudo systemctl edit homelab-backup.service

Add:

[Unit]
RequiresMountsFor=/mnt/backup
AssertPathIsMountPoint=/mnt/backup

Then reload and inspect the result:

sudo systemctl daemon-reload
systemctl cat homelab-backup.service
systemctl show homelab-backup.service -p Requires -p After

According to the systemd unit documentation, RequiresMountsFor= adds requirement and ordering dependencies for the mount units needed to reach a path. With a correctly declared mount, systemd attempts to bring that storage up before starting the service.

It cannot infer that an ordinary directory was supposed to contain an undeclared disk. The assertion checks the exact path too, refusing startup if it is not a mount point.

Why an assertion instead of ConditionPathIsMountPoint=? An unmet condition skips startup rather than reporting an ordinary service failure. For a backup, I want an unsuccessful assertion to be visible instead of treating missing storage as an acceptable reason to do nothing.

Check support in your host’s installed man systemd.unit. Older distributions may lack some path checks; the script guard remains useful. Assertions are evaluated at startup, not continuously during a running backup.

If the service is launched by a systemd timer, the timer can stay enabled. The storage requirements belong to the service doing the work, not just its scheduler.

Test the refusal without touching your real disk

Do not unmount a live backup filesystem just to prove a point. Start with an empty temporary directory and a harmless shell check:

test_dir=$(mktemp -d)
if mountpoint -q -- "$test_dir"; then
  printf 'Unexpected mount point; investigate before continuing\n' >&2
else
  printf 'PASS: an ordinary directory is rejected\n'
fi
rmdir -- "$test_dir"

Then test the systemd assertion with a transient service. The deliberately missing path must not already be a mount point:

sudo systemd-run --wait --collect \
  --unit=backup-mount-guard-test \
  --property=AssertPathIsMountPoint=/run/backup-mount-guard-missing \
  /usr/bin/true

Expect startup to fail because the assertion is unmet. This does not run a backup or modify your real storage. If the command succeeds, inspect the path and installed systemd behavior before trusting your configuration.

For the positive case, verify your actual mount, run a small disposable backup using your normal backup tool, and restore it to a separate directory. A guard test proves refusal behavior; only a restore test proves that the backup artifact is useful.

Missing storage must produce an alert

A nonzero exit code sitting in yesterday’s journal is not much of a warning. Inspect the service after a controlled run:

systemctl status homelab-backup.service
journalctl -u homelab-backup.service --since today --no-pager

Connect failure reporting to your monitoring system. Also monitor the age of the last successful backup from outside the backup host where possible: a machine that never boots cannot send its own failure notification.

Do not send a success heartbeat unconditionally after the backup command. Keep it inside the success branch, and make sure pipeline or wrapper logic does not hide the tool’s failure. If jobs can overlap, a flock-based lock solves that separate problem; a mount guard does not.

Keep remote-access security separate

If you administer backup hosts while traveling, protect the connection too. A commercial VPN can be useful for traffic from a laptop on an untrusted network, but it neither validates your backup destination nor automatically gives you private access to your server.

🚀NordVPN

An optional VPN for your laptop on untrusted networks, not a replacement for backup checks or server access controls.

Explore NordVPN →

Affiliate link — we may earn a commission at no extra cost to you.

For private server connectivity, choose an appropriate SSH or mesh-VPN setup separately. Do not route backup traffic through a commercial VPN merely because a tutorial includes an affiliate offer.

Your next backup should fail safely

My recommendation is simple: pick one backup job today and inspect what happens when its destination is unavailable. Do not start by adding another dashboard.

Declare the mount, add the script guard, connect the service dependencies, and test refusal with a disposable path. Then perform a restore and verify that missed successful runs trigger an alert.

A failed backup is bad. A backup that quietly fills the wrong disk while pretending to protect your data is worse.

Stay in the loop 📬

Get self-hosting tutorials, tool reviews, and infrastructure tips delivered to your inbox. No spam, unsubscribe anytime.

Join 0 self-hosters. Free forever.