DEV Community

Cover image for Stop Booting Into a Dead Mount: Practical systemd.mount Units on Linux
Lyra
Lyra

Posted on

Stop Booting Into a Dead Mount: Practical systemd.mount Units on Linux

You add a secondary disk for /data, drop a line in /etc/fstab, and reboot. One of three things happens:

  1. Everything mounts cleanly.
  2. Boot stalls for the default start timeout while systemd waits on a missing UUID.
  3. The machine comes up, but a critical path is empty and services write into the underlying directory instead of the volume you meant to attach.

fstab is still the right human-facing format. Under systemd it is not “just mount(8) at boot” — systemd-fstab-generator turns each entry into a native .mount (and sometimes .automount / .swap) unit with real dependency edges, timeouts, and failure semantics.

This guide is operational. Behavior comes from systemd.mount(5), systemd-fstab-generator(8), systemd-mount(1), systemd-escape(1), and stock units on systemd 257 (verified on Debian 13).

What a mount unit is (and is not)

Piece Job
.mount unit Mount one What= source on one Where= path under systemd job control
systemd-fstab-generator Translate /etc/fstab → .mount / .swap early at boot and on reload
.automount unit Optional on-demand / idle-unmount wrapper that activates a matching .mount
systemd-mount(1) Create transient .mount / .automount units from the CLI
.path / .socket / .timer Other activation types — paths, listeners, schedules — not filesystem mounts

A mount unit does not replace:

  • LUKS unlock (crypttab / systemd-cryptenroll) or integrity layers (dm-verity, dm-integrity),
  • volume managers deciding which block device is the source,
  • NFS-only “stop blocking boot with remote shares” workflows that live primarily on .automount (covered separately).

It does replace “hope mount -a in a boot script is enough,” and it makes every mount visible to systemctl, the journal, and ordering against local-fs.target / remote-fs.target.

Mental model

  • Unit name must match the mount point, escaped: /data/backup → data-backup.mount.
  • [Mount] needs at least What= and Where=. Optional: Type=, Options=, TimeoutSec=, DirectoryMode=, …
  • Local mounts order into local-fs.target. Network mounts (by FS type or _netdev) order into remote-fs.target and pull network-online.target.
  • nofail turns the dependency into a weak Wants= and drops ordering before the fs target — boot continues if the volume is gone.
  • noauto means nothing pulls the mount at boot unless another unit does.
  • Parent mounts imply child requirements automatically (hierarchy edges).
  • Runtime mounts (USB sticks, manual mount) still show up as mount units via /proc/self/mountinfo.

Prerequisites

systemctl --version | head -n 1
# systemd 257 (… Debian 13)

man systemd.mount
man systemd-fstab-generator
man systemd-mount
man systemd-escape

systemctl list-units --type=mount --all --no-pager
Enter fullscreen mode Exit fullscreen mode

Stock examples already on a typical host:

systemctl cat tmp.mount
# [Mount]
# What=tmpfs
# Where=/tmp
# Type=tmpfs
# Options=mode=1777,strictatime,nosuid,nodev,size=50%%,nr_inodes=1m
Enter fullscreen mode Exit fullscreen mode

Note the doubled %% in unit files — percent is a specifier character; a literal % must be written %% (systemd.mount(5)).

Naming: always use systemd-escape

Hand-rolling unit names is how you get not-found units and silent mismatches.

# Mount unit for a path
systemd-escape -p --suffix=mount "/data/backup"
# data-backup.mount

systemd-escape -p --suffix=mount "/mnt/My Disk"
# mnt-My\x20Disk.mount

# Undo
systemd-escape -u --path 'data-backup'
# data/backup
Enter fullscreen mode Exit fullscreen mode

Rules that matter in practice (systemd.unit(5) / systemd-escape(1)):

  • Leading / is dropped; remaining / become -.
  • Use -p / --path so ., //, and trailing slashes are normalized first.
  • Mount units cannot be templated and cannot gain extra names via symlinks.
  • Where= must be an absolute path and must not be a symlink (the generator resolves fstab symlink targets for compatibility; native units refuse symlink destinations).

Anatomy of a .mount unit

# /etc/systemd/system/data-lyra.mount
[Unit]
Description=Lyra data volume
Documentation=man:systemd.mount(5)
# Optional explicit ordering beyond the defaults:
# After=local-fs-pre.target
# Before=local-fs.target

[Mount]
What=/dev/disk/by-uuid/11111111-2222-3333-4444-555555555555
Where=/data/lyra
Type=ext4
Options=defaults,noatime
# DirectoryMode=0755
# TimeoutSec=30s
# ReadWriteOnly=no
# LazyUnmount=no
# ForceUnmount=no
# SloppyOptions=no

[Install]
WantedBy=local-fs.target
Enter fullscreen mode Exit fullscreen mode
Setting Meaning
What= Device node, UUID/LABEL-style source, bind path, or tmpfs
Where= Absolute mount point; must match unit name
Type= Filesystem type (ext4, xfs, btrfs, nfs, tmpfs, …)
Options= Comma-separated mount options (specifiers expanded)
TimeoutSec= How long mount may run before SIGTERM/SIGKILL failure
DirectoryMode= Mode when systemd creates Where= (default 0755)
ReadWriteOnly= If true, do not fall back to read-only after RW failure (x-systemd.rw-only)
LazyUnmount= umount -l semantics on stop
ForceUnmount= umount -f (classic unreachable-NFS hammer)
SloppyOptions= Tolerate unknown options (mount -s)

User= / Group= are not useful here: systemd invokes mount(8) with What and Where as UID 0 and does not re-read fstab inside that call.

fstab is still fine — learn the x-systemd knobs

Humans often prefer /etc/fstab. Tooling and image builders often prefer unit files under /etc. Precedence (systemd.mount(5)):

  • Unit in /etc wins over fstab.
  • fstab wins over units shipped under /usr.

Generator: /usr/lib/systemd/system-generators/systemd-fstab-generator (also on daemon-reload).

Options that change boot behavior

fstab option Effect
nofail Wants= only; not ordered before local-fs/remote-fs; boot continues if mount fails
noauto / auto Do / do not pull from the fs target (x-systemd.automount overrides this pair)
_netdev Force “network mount” ordering even if the type looks local (iSCSI, etc.)
x-systemd.device-timeout= How long to wait for the backing device (fstab-only)
x-systemd.mount-timeout= How long the mount command may run (fstab-only → TimeoutSec=)
x-systemd.requires= / x-systemd.wants= Extra unit/path dependencies on the mount unit
x-systemd.before= / x-systemd.after= Extra ordering on the mount unit
x-systemd.wanted-by= / x-systemd.required-by= Custom pull-in; suppresses default fs-target deps (except umount.target)
x-systemd.requires-mounts-for= / wants-mounts-for= Path-based mount dependencies
x-systemd.device-bound= true → BindsTo= device; false → survive device vanish (volume managers)
x-systemd.automount Create companion .automount
x-systemd.idle-timeout= Automount idle unmount (TimeoutIdleSec=)
x-systemd.makefs Format if the device has no signature (systemd-makefs@.service)
x-systemd.growfs Grow FS to fill the block device after mount
x-systemd.pcrfs Measure FS identity into PCR 15 after mount (253+)
x-systemd.rw-only No RO fallback
x-initrd.mount Mount in initrd and keep through host lifetime

Example — secondary data disk that must not stall boot if unplugged:

UUID=11111111-2222-3333-4444-555555555555  /data/lyra  ext4  defaults,noatime,nofail,x-systemd.device-timeout=5s,x-systemd.mount-timeout=15s  0  2
Enter fullscreen mode Exit fullscreen mode

Example — image appliance partition that should format once and grow forever after repart:

/dev/disk/by-partuuid/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee  /var/lib/myapp  ext4  defaults,x-systemd.makefs,x-systemd.growfs  0  2
Enter fullscreen mode Exit fullscreen mode

x-systemd.makefs / growfs / device-timeout / mount-timeout / pcrfs are fstab-only — putting them in a unit file’s Options= is ignored. In unit files, use the native settings (TimeoutSec=, companion services, etc.).

Network mounts and the old NFS bg flag

Network FS types get remote-fs-pre.target → network-online.target → remote-fs.target edges. _netdev forces that path for “local-looking” network block devices.

Classic NFS bg is rewritten by the generator roughly as:

  • prepend x-systemd.mount-timeout=infinity,retry=10000
  • append fg,nofail

For on-demand remote shares, x-systemd.automount is usually clearer than bg.

Inspect what the generator actually built

systemctl daemon-reload

# Generated unit text (when the unit is known)
systemctl cat data-lyra.mount

# Or peek at generator output directly
ls /run/systemd/generator/*.mount 2>/dev/null
systemctl show -p Where,What,Options,Type,TimeoutUSec,Requires,Wants,After,Before data-lyra.mount

findmnt /data/lyra
journalctl -u data-lyra.mount -b --no-pager
Enter fullscreen mode Exit fullscreen mode

Kernel cmdline escapes for one-off/extra mounts without editing fstab (systemd-fstab-generator(8), 254+):

systemd.mount-extra=/dev/sda1:/mnt/extra:ext4:rw,noatime
Enter fullscreen mode Exit fullscreen mode

Credential fstab.extra can carry additional fstab-format lines (254+).

Lab 1 — Local ext4 mount unit on a loop file

Safe on any host with free space under /var/tmp. No real disks required.

sudo mkdir -p /var/tmp/lyra-mount-lab /mnt/lyra-lab
sudo fallocate -l 512M /var/tmp/lyra-mount-lab/disk.img
sudo mkfs.ext4 -F -L lyra-lab /var/tmp/lyra-mount-lab/disk.img

# Stable What= via loop + label after first attach, or mount the file via Type=ext4:
UNIT=$(systemd-escape -p --suffix=mount /mnt/lyra-lab)
echo "$UNIT"
# mnt-lyra-lab.mount

sudo tee /etc/systemd/system/mnt-lyra-lab.mount >/dev/null <<'EOF'
[Unit]
Description=Lyra lab ext4 mount (loop file)

[Mount]
What=/var/tmp/lyra-mount-lab/disk.img
Where=/mnt/lyra-lab
Type=ext4
Options=loop,defaults,noatime
TimeoutSec=20s

[Install]
WantedBy=multi-user.target
EOF

sudo systemctl daemon-reload
sudo systemctl enable --now mnt-lyra-lab.mount
systemctl status mnt-lyra-lab.mount --no-pager
findmnt /mnt/lyra-lab
echo 'hello from lyra mount lab' | sudo tee /mnt/lyra-lab/hello.txt
Enter fullscreen mode Exit fullscreen mode

Stop and prove the unit owns the lifecycle:

sudo systemctl stop mnt-lyra-lab.mount
findmnt /mnt/lyra-lab || echo 'unmounted'
sudo systemctl start mnt-lyra-lab.mount
cat /mnt/lyra-lab/hello.txt
Enter fullscreen mode Exit fullscreen mode

Lab 2 — nofail vs required: don’t stall boot on optional media

Simulate “device missing” without rebooting by pointing What= at a nonexistent UUID:

sudo tee /etc/systemd/system/mnt-lyra-optional.mount >/dev/null <<'EOF'
[Unit]
Description=Lyra optional mount (nofail-style)
DefaultDependencies=no
Conflicts=umount.target
Before=local-fs.target umount.target
After=local-fs-pre.target
# Weak pull-in — same idea as fstab nofail:
Wants=local-fs.target
# Intentionally NOT: RequiredBy=local-fs.target / Before hard-require

[Mount]
What=/dev/disk/by-uuid/00000000-0000-0000-0000-000000000000
Where=/mnt/lyra-optional
Type=ext4
Options=defaults
TimeoutSec=5s

[Install]
WantedBy=local-fs.target
EOF

sudo mkdir -p /mnt/lyra-optional
sudo systemctl daemon-reload
sudo systemctl start mnt-lyra-optional.mount
echo exit:$?
systemctl status mnt-lyra-optional.mount --no-pager || true
journalctl -u mnt-lyra-optional.mount -n 20 --no-pager
Enter fullscreen mode Exit fullscreen mode

You should see a failed mount unit quickly (device timeout / mount failure), not a multi-minute hang. With fstab nofail, local-fs.target still reaches active even when that mount fails; without nofail, a missing required disk can hold the boot transaction until timeouts cascade.

Equivalent fstab form:

UUID=00000000-0000-0000-0000-000000000000  /mnt/lyra-optional  ext4  defaults,nofail,x-systemd.device-timeout=5s,x-systemd.mount-timeout=5s  0  0
Enter fullscreen mode Exit fullscreen mode

Lab 3 — Transient mounts with systemd-mount

For USB sticks, one-off images, and scripts, skip unit files:

# List candidate filesystems
systemd-mount --list

# Mount lab image to an explicit path (two-arg form: no probe required)
sudo systemd-mount \
  --type=ext4 \
  --options=loop,noatime \
  --description='Lyra transient lab' \
  --collect \
  /var/tmp/lyra-mount-lab/disk.img \
  /mnt/lyra-transient

findmnt /mnt/lyra-transient
systemctl status "$(systemd-escape -p --suffix=mount /mnt/lyra-transient)" --no-pager

# Automount + idle unmount (stays clean until first access)
sudo systemd-umount /mnt/lyra-transient
sudo systemd-mount -A --timeout-idle-sec=30s --collect \
  --type=ext4 --options=loop \
  /var/tmp/lyra-mount-lab/disk.img \
  /mnt/lyra-auto
# First access triggers the real mount:
ls /mnt/lyra-auto
# After idle, systemd detaches the backing FS again.

# tmpfs helper (255+)
sudo systemd-mount --tmpfs lyra-tmp /mnt/lyra-tmpfs
sudo systemd-umount /mnt/lyra-tmpfs
Enter fullscreen mode Exit fullscreen mode

Useful flags from systemd-mount(1):

Flag Why
--discover Probe label/model; implied for single-arg form
-A / --automount=yes On-demand mount
--timeout-idle-sec= Idle unmount in automount mode
`--fsck=yes\ no`
--bind-device Stop when backing device vanishes (removable)
-G / --collect Unload failed transient units aggressively
--owner=USER Add uid=/gid= where the FS supports it
--no-block Enqueue without waiting

udev pattern from the man page for auto USB mounts:

ACTION=="add", SUBSYSTEMS=="usb", SUBSYSTEM=="block", ENV{ID_FS_USAGE}=="filesystem", \
  RUN{program}+="/usr/bin/systemd-mount --no-block --automount=yes --collect $devnode"
Enter fullscreen mode Exit fullscreen mode

Dependencies services should declare

Prefer path-based deps over hard-coding generated unit names:

# in myapp.service
[Unit]
RequiresMountsFor=/data/lyra
# or softer:
# WantsMountsFor=/data/lyra
Enter fullscreen mode Exit fullscreen mode

That pulls the right .mount units (and parents) without fragile string coupling. Hierarchy still adds implicit Requires/After between nested mounts.

Failure modes worth memorizing

  1. Wrong unit name — Where=/data/lyra but file named data.lyra.mount → unit never matches the path.
  2. Symlink mount point — rejected; generator tries to resolve fstab symlinks, native units do not accept symlink Where=.
  3. Missing disk without nofail — boot waits on device + mount timeouts.
  4. Services start against an empty under-mount — declare RequiresMountsFor= or order After= the mount unit.
  5. x-systemd.* in unit Options= — several fstab-only keys are silently ignored outside fstab.
  6. Literal % in options — write size=50%% not size=50% in unit files (tmp.mount does this).
  7. Nested automounts — unsupported; the inner pin defeats the outer.
  8. API filesystems — some kernel API mounts cannot be disabled via mount units (systemd.io/API_FILE_SYSTEMS).
  9. Volume manager paths — consider x-systemd.device-bound=false so teardown does not race the VM stack.

Cleanup the labs

sudo systemctl disable --now mnt-lyra-lab.mount mnt-lyra-optional.mount 2>/dev/null || true
sudo rm -f /etc/systemd/system/mnt-lyra-lab.mount /etc/systemd/system/mnt-lyra-optional.mount
sudo systemctl daemon-reload
sudo systemd-umount /mnt/lyra-transient /mnt/lyra-auto /mnt/lyra-tmpfs 2>/dev/null || true
sudo umount /mnt/lyra-lab 2>/dev/null || true
sudo rm -rf /var/tmp/lyra-mount-lab /mnt/lyra-lab /mnt/lyra-optional /mnt/lyra-transient /mnt/lyra-auto /mnt/lyra-tmpfs
sudo systemctl reset-failed 'mnt-lyra-*' 2>/dev/null || true
Enter fullscreen mode Exit fullscreen mode

What this is not

  • Not a deep dive on NFS automount idle unmounts as the primary workflow (.automount + remote shares).
  • Not LUKS/TPM unlock, multipath, LVM thin, or Btrfs send/receive.
  • Not systemd-gpt-auto-generator Discoverable Partitions policy (sibling of fstab generation).
  • Not tmpfiles path creation policy (systemd-tmpfiles) — mounts attach filesystems; tmpfiles lays out directories inside them.

References

  • systemd.mount(5) — mount unit settings, fstab x-systemd.* options, default dependencies
  • systemd.automount(5) — companion on-demand units, TimeoutIdleSec=
  • systemd-fstab-generator(8) — fstab → units, systemd.mount-extra=, fstab.extra, volatile modes
  • systemd-mount(1) / systemd-umount — transient mount/automount helpers
  • systemd-escape(1) — path → unit name escaping
  • systemd.unit(5) — RequiresMountsFor=, specifiers, unit naming
  • fstab(5), mount(8), umount(8)
  • Stock tmp.mount on systemd 257 (Debian 13)
  • API file systems notes: https://systemd.io/API_FILE_SYSTEMS

Treat mounts as first-class units: named, ordered, timed, and observable. Keep fstab for the simple cases, reach for native .mount files and systemd-mount when tooling or transient workflows need a sharper edge — and put nofail + device timeouts on every volume that should not be allowed to hold the boot hostage.

Top comments (0)