Running a nightly backup script after logout with systemd user units – no nohup needed

Running a nightly backup script after logout with systemd user units – no nohup needed

I set up a little home‑lab server last year and wanted a way to snap my home directory every night without tying the job to a terminal. The old nohup trick works, but it leaves orphaned processes and you end up chasing logs in the dark. Systemd user units give a cleaner, more reliable solution that plays nicely with the rest of the system.

Why use a systemd user unit?

  • Automatic cleanup – When you log out, systemd kills any processes started by the unit, so you never end up with stray backup jobs.
  • Fine‑grained control – You can set OnCalendar, ExecStart, Restart, and StandardOutput just like a system service.
  • No extra daemons – The user instance of systemd is already running for every logged‑in user, so you don’t need to start a separate service manager.
  • Security isolation – The unit runs in the user namespace, limiting what it can see and do compared to a root‑level service.

Quick setup

Assume your backup script sits at ~/scripts/backup.sh and produces its own logs.

chmod +x ~/scripts/backup.sh

1. Create the unit file

User units live in ~/.config/systemd/user/. Drop a file called backup-nightly.service there:

[Unit]
Description=Nightly backup of home directory
After=network-online.target

[Service]
Type=oneshot
ExecStart=/home/youruser/scripts/backup.sh
StandardOutput=append:/home/youruser/backup.log
StandardError=append:/home/youruser/backup.err
# If you want the job to retry on failure
Restart=on-failure
RestartSec=5s

[Install]
WantedBy=default.target
  • After=network-online.target makes sure the network is up before the backup starts.
  • StandardOutput/StandardError send output straight to log files; append: keeps old data.
  • Restart=on-failure is optional; if the script exits non‑zero, systemd will retry after 5 s.

2. Create a timer unit

The timer triggers the service. In the same directory, create backup-nightly.timer:

[Unit]
Description=Run nightly backup at 02:30

[Timer]
OnCalendar=*-*-* 02:30:00
Persistent=true

[Install]
WantedBy=timers.target

Persistent=true guarantees the job runs if the machine was off at the scheduled time.

3. Reload, enable, and start

systemctl --user daemon-reload
systemctl --user enable --now backup-nightly.timer

Verify the timer:

systemctl --user list-timers | grep backup-nightly

The first run will happen at the next 02:30. After that, systemd takes care of the rest.

What the backup script should look like

A minimal example that uses rsync:

#!/usr/bin/env bash
set -euo pipefail

SRC="$HOME/"
DST="/mnt/backup/home-$(date +%Y%m%d)/"

mkdir -p "$DST"
rsync -aAXv --delete "$SRC" "$DST"
  • -aAXv preserves permissions, ACLs, and extended attributes.
  • --delete keeps the destination clean.
  • The script exits with a non‑zero status if rsync fails, triggering the Restart=on-failure logic.

If you prefer btrfs snapshots, swap the rsync line for a btrfs subvolume snapshot command. The unit file stays the same.

Security considerations

  • Least‑privilege – The unit runs as your user, not root. If the backup needs to touch root‑only files, add a dedicated sudo rule that only allows the script to run as root.
  • File permissions – Keep the script and logs readable only by you: chmod 700 backup.sh and chmod 600 backup.log backup.err.
  • Network – If the backup writes to a remote server via SSH, use key‑based authentication and restrict the key’s permissions (chmod 600 ~/.ssh/id_rsa).
  • Audit – Log the start and end times inside the script. A simple echo "$(date) – backup finished" >> ~/backup.log helps spot silent failures.

Common pitfalls and troubleshooting

Symptom Likely cause Fix
Timer never fires systemd --user not started Log in via a graphical session or run systemctl --user enable --now after login.
Service fails with “Permission denied” Script or log file permissions Ensure the script is executable and logs are writable.
Backup runs but no data copied Wrong source path Verify $SRC expands correctly; add echo "$SRC" to the script for debugging.
Logs grow unchecked StandardOutput set to append: Rotate logs with logrotate or change to StandardOutput=journal to use systemd’s journal.

Use journalctl --user -u backup-nightly.service to inspect recent runs. The -b flag shows only the current boot, which is handy for debugging after a reboot.

Trade‑offs compared to nohup

Feature nohup Systemd user unit
Process cleanup Manual kill Automatic on logout
Logging Manual redirection Built‑in StandardOutput
Scheduling cron or at systemd.timer
Security isolation None User namespace
Resource limits None LimitCPU, LimitFSIZE, etc.

If you only need a one‑off backup, nohup is fine. For recurring jobs, systemd units are more maintainable and integrate with the rest of the system.

Happy backing up, and keep your logs tidy!


See also