How to Schedule Backups and Maintenance with systemd Timers

Automate recurring Linux tasks with built-in systemd timers so backups, cleanup jobs, and health scripts run on schedule and leave useful logs behind.

systemdAutomationMaintenance
What you learn

How to create a service unit and matching timer, test it safely, inspect its logs, and use it for backup or maintenance jobs.

Best for

Restic backups, cleanup scripts, health checks, cache pruning, report generation, and other recurring tasks on Linux servers.

Risk to watch

Scheduled automation can fail quietly if you never check logs or test the command manually first.

Before you begin

  • A Linux machine that uses systemd.
  • A command or script that already works when run manually.
  • Permission to create files in /etc/systemd/system/ and restart systemd units with sudo.
  • A clear answer to what should happen if the job fails.

systemd timers are a strong alternative to traditional cron jobs because they integrate with the same service management and logging tools you already use for Linux services. Instead of scattering commands across crontabs and wondering where output went, you can define a named service, attach a schedule to it, and inspect results through systemctl and journalctl.

Why this matters for real operations

Backups and maintenance tasks are often treated as “set and forget” jobs. That is exactly why they fail quietly. A timer-based workflow is better because it is easier to understand later. You can ask whether the timer is enabled, when it last ran, when it will run next, and what it logged during the last execution.

Expected outcome: By the end of this guide, you will have a reusable timer pattern that can run a backup or cleanup script daily and give you logs you can inspect with one command.

Step 1: Create and test the task script

Always start with a script that succeeds manually before you schedule it. Here is a simple example backup script:

sudo mkdir -p /usr/local/bin
sudo nano /usr/local/bin/myapp-backup.sh

Example script contents:

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

TIMESTAMP=$(date -u +%Y-%m-%d-%H%M%S)
BACKUP_DIR=/var/backups/myapp
SOURCE_DIR=/opt/myapp/data

mkdir -p "$BACKUP_DIR"
tar -czf "$BACKUP_DIR/myapp-$TIMESTAMP.tar.gz" "$SOURCE_DIR"
find "$BACKUP_DIR" -type f -name 'myapp-*.tar.gz' -mtime +7 -delete

echo "Backup completed: $BACKUP_DIR/myapp-$TIMESTAMP.tar.gz"

Make it executable:

sudo chmod 755 /usr/local/bin/myapp-backup.sh

Run it manually first:

sudo /usr/local/bin/myapp-backup.sh
ls -lah /var/backups/myapp

If the command fails here, fixing it now is much easier than trying to debug a scheduled failure later.

Step 2: Create the service unit and timer

Create a service unit that runs the script on demand:

sudo nano /etc/systemd/system/myapp-backup.service

Use contents like:

[Unit]
Description=Run backup for myapp

[Service]
Type=oneshot
ExecStart=/usr/local/bin/myapp-backup.sh
User=root

Then create the timer:

sudo nano /etc/systemd/system/myapp-backup.timer

Example timer:

[Unit]
Description=Daily backup timer for myapp

[Timer]
OnCalendar=*-*-* 03:15:00
Persistent=true
RandomizedDelaySec=5m
Unit=myapp-backup.service

[Install]
WantedBy=timers.target

Important fields:

  • OnCalendar sets the schedule.
  • Persistent=true tells systemd to run the missed job after boot if the machine was off when the timer should have fired.
  • RandomizedDelaySec helps avoid every task starting at the exact same second.
  • Type=oneshot fits commands that run and exit.

If the task should run as a non-root user, create a dedicated user and set User= accordingly. Avoid root when it is unnecessary.

Step 3: Enable, test, and verify the timer

Reload systemd after creating the files:

sudo systemctl daemon-reload

Test the service directly first:

sudo systemctl start myapp-backup.service
sudo systemctl status myapp-backup.service
sudo journalctl -u myapp-backup.service -n 50 --no-pager

If that works, enable the timer:

sudo systemctl enable --now myapp-backup.timer

Check its schedule:

systemctl list-timers --all | grep myapp-backup

You should see when it last ran and when it will run next. That visibility is one of the best reasons to use timers instead of mysterious old cron lines.

You can apply the same pattern to other maintenance tasks, such as:

  • Running a Restic backup script every night
  • Pruning old temporary files
  • Refreshing a cache or search index
  • Generating reports or exports
  • Checking disk usage and writing a summary log

Expected outcomes and what success looks like

When the setup is healthy, you should be able to confirm all of the following:

  • The service runs successfully when started manually.
  • The timer is enabled and listed in systemctl list-timers.
  • The journal shows a clear record of each run.
  • The backup or maintenance artifact appears where expected.
  • The task still makes sense after a reboot because the timer persists and the script paths are absolute.

That last point matters. Scheduled jobs fail surprisingly often because they rely on relative paths, shell-specific assumptions, or environment variables that only existed in an interactive session.

Troubleshooting common timer mistakes

The timer is enabled but never runs.
Check systemctl list-timers --all and confirm the OnCalendar syntax is valid. Also verify the system clock and time zone are what you expect.

The service works manually in my shell but fails from systemd.
Use absolute paths for scripts, binaries, and files. systemd runs with a different environment than your interactive shell.

No logs appear where I expected.
Read the journal for the service unit directly with journalctl -u myapp-backup.service. stdout and stderr from the service go there by default.

The job missed its schedule during downtime.
Make sure Persistent=true is set if you want catch-up behavior after boot.

Warning: Do not automate a destructive cleanup command until you have run it manually and confirmed its path handling. Automation multiplies both good habits and bad ones.

What to do next

Once timers are in place, a smart next step is pairing them with log review and alerting so failures do not stay silent. Continue with How to Build a Simple Uptime Dashboard with Uptime Kuma.