Most blog entries on SystemD timers give trivial samples. This article takes a different approach and provides the full details of two examples of using SystemD timers that are in active use on my systems.
- Knowledge of basic Linux system administration for a SystemD-based distribution.
- For the first example (one could of course substitute a different software package and adjust the example to suit):
- We use
restic, so to follow along exactly one should understand the restic docs.
- The latest version of restic installed to
- A user named
resticwith primary group
resticand home directory
- A password for a restic repository defined in
- Restic repository access information defined in
- We use
- For the second example we execute a command that pipes to stdout (which we redirect to a FIFO), which becomes (through the FIFO) stdin for the
resticcommand that backs the data sent in on stdin to the encrypted restic backup repository.
- In addition to basically the same prerequisites (but a different password-file and repository-file) as the first example, the second example requires:
- Gitea installed and running as user
giteawith primary group
- gitea user has home directory
- gitea ‘work dir’ is
- Gitea installed and running as user
A daily backup of the select parts of a Linux system using
AmbientCapabilities=CAP_DAC_READ_SEARCH enables the task to access read all files on the system even though the user is ‘only’
restic and not
The task to execute each day
This goes in
[Unit] Description=Execute restic once a day [Service] # Only execute a single time per timer trigger (from the .timer file) Type=oneshot # Act at quite a low priority so that when this triggers it doesn't # interfere with the primary functions of the device/VM Nice=17 # Executes the actual backup run (restic binary) # Adjust the --exclude options and directories to include as suits # your system ExecStart=/usr/local/sbin/restic --repository-file /home/restic/repository-file --password-file /home/restic/password-file --cleanup-cache --quiet backup --one-file-system --exclude-caches --exclude /root/.cache --exclude /var/lib/libvirt --exclude /var/lib/docker --exclude /var/lib/postgresql /etc /root /var/backups /var/lib /var/local /var/lock /var/log /var/mail /var/opt /var/spool /srv/gitea/home /home User=restic Group=restic AmbientCapabilities=CAP_DAC_READ_SEARCH # Everything that follows this comment is about sandboxing restic as much # as possible for a program that is doing a system-level backup. For # details see the systemd.exec(1) man page SystemCallFilter=@basic-io @aio @debug @file-system @io-event @ipc @network-io @obsolete @privileged @process @resources @signal @sync @timer SystemCallErrorNumber=EPERM ProtectSystem=full ReadWritePaths=/home/restic/.cache PrivateTmp=true NoNewPrivileges=yes ProtectHostname=yes ProtectClock=yes ProtectKernelTunables=yes ProtectKernelModules=yes ProtectKernelLogs=yes ProtectControlGroups=yes RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6 MemoryDenyWriteExecute=yes SystemCallArchitectures=native RestrictSUIDSGID=yes PrivateMounts=yes
The SystemD Timer (this triggers the task once a day)
It goes in
[Timer] OnActiveSec=24h # Time to wait before triggering restic-daily.service, # once after boot OnUnitActiveSec=24h # Time to wait between executions of # restic-daily.service RandomizedDelaySec=300 # 0-300 second delay added to each execution delay Persistent=yes [Install] WantedBy=timers.target
Enabling the SystemD Restic Timer Task
systemctl daemon-reload && systemctl enable --now restic-daily.timer will start the timer, which will cause an execution in 24h + a delay of 0-300 seconds, followed by execution every 24h thereafter (with a 0-300 second delay).
The .service files for a Gitea dump to restic
Task to create FIFO if it does not already exist
[Unit] Description=Create Gitea Dump FIFO # Only execute the command below if the FIFO does not exist ConditionPathExists=!/run/gitea/gitea_db_dump [Service] # Only execute the command once per trigger (either of the other # .service files for this example) Type=oneshot # Make sure the directory containing the FIFO exists and is # only accessible by the owner ExecStartPre=mkdir -m 0700 -p /run/gitea # Make sure the FIFO exists ExecStart=mkfifo -m 0600 /run/gitea/gitea_db_dump
The Gitea task to periodically execute (stdout)
[Unit] Description=Dump Gitea data once an hour # Execute after the FIFO has been created After=gitea-backup-fifo.service # Execute gitea-backup-fifo.service (make the FIFO) if this has not # already been done Requires=gitea-backup-fifo.service [Service] Environment="USER=gitea" "HOME=/srv/gitea/home" "GITEA_WORK_DIR=/srv/gitea/data" # Only execute a single time per timer trigger # (from the restic-gitea-backup.service file) Type=oneshot # Act at quite a low priority so that when this triggers it doesn't # interfere with the primary functions of the device/VM Nice=17 User=gitea Group=gitea # Send logs to the systemd journal StandardError=journal # Send stdout (the tarball generated below and sent to stdout via '-f -') to FIFO StandardOutput=file:/run/gitea/gitea_db_dump # The actual gitea dump to a tarball on stdout ExecStart=/usr/local/sbin/gitea --config /etc/gitea/app.ini dump --custom-path /srv/gitea/data/custom --work-path /srv/gitea/data --type tar.gz -f - # Everything that follows this comment is about sandboxing gitea as much # as possible. For details see the systemd.exec(1) man page SystemCallFilter=@system-service SystemCallErrorNumber=EPERM NoNewPrivileges=yes ProtectHostname=yes ProtectClock=yes ProtectKernelTunables=yes ProtectKernelModules=yes ProtectKernelLogs=yes ProtectControlGroups=yes MemoryDenyWriteExecute=yes SystemCallArchitectures=native RestrictSUIDSGID=yes
The restic task to periodically execute (stdin)
Description=Receive Gitea Data Dump # Execute after the FIFO has been created After=gitea-backup-fifo.service # Execute gitea-backup-fifo.service (make the FIFO) if this has not # already been done. # Also start restic-gitea-dump.service since we need it to consume # the data we generate (to store in backup) Requires=gitea-backup-fifo.service restic-gitea-dump.service [Service] # Only execute a single time per timer trigger (from the .timer file) Type=oneshot # Act at quite a low priority so that when this triggers it doesn't # interfere with the primary functions of the device/VM Nice=17 User=restic Group=restic # See comments from Example #1, but we using --stdin and with filename # /gitea-dump.tar.gz rather than specifying filesystem paths to # include or exclude. ExecStart=/usr/local/sbin/restic --repository-file /home/restic/gitea-restic-files/repository-file --password-file /home/restic/gitea-restic-files/password-file --cleanup-cache --quiet backup --stdin-filename /gitea-dump.tar.gz --stdin # Send stdout and stderr (logs) to SystemD journal StandardOutput=journal StandardError=journal StandardInput=file:/run/gitea/gitea_db_dump # Everything that follows this comment is about sandboxing restic as much # as possible when using a FIFO. For details see # the systemd.exec(1) man page SystemCallFilter=@basic-io @aio @debug @file-system @io-event @ipc @network-io @obsolete @privileged @process @resources @signal @sync @timer SystemCallErrorNumber=EPERM NoNewPrivileges=yes ProtectHostname=yes ProtectClock=yes ProtectKernelTunables=yes ProtectKernelModules=yes ProtectKernelLogs=yes ProtectControlGroups=yes MemoryDenyWriteExecute=yes SystemCallArchitectures=native RestrictSUIDSGID=yes
The .timer file for a Gitea dump to restic
[Timer] OnActiveSec=1h # Set to time after boot after which to launch first backup OnUnitActiveSec=1h # Set this to how often to repeat # backup task (1/hour in this example) RandomizedDelaySec=120 # 0-120 seconds delay (random) [Install] WantedBy=timers.target
Enabling the SystemD Gitea Backup Timer Task
systemctl daemon-reload && systemctl enable --now restic-gitea-backup.timer will start the timer.
The timer will trigger
restic-gitea-backup.service which will ensure
gitea-backup-fifo.service is triggered along with
restic-gitea-dump.service (which will make sure it occurs after
This results in the FIFO being created and
restic-gitea-dump dumping Gitea’s data into the FIFO as
restic-gitea-backup pulls it out of the FIFO and stores it in the encrypted backup repository.
Credit for cover photo of a silver wind-up alarm clock with a white face and black time markings and hands on a lichen-covered rock with an out of focus meadow in the background by pasja1000 via Pixabay.