For scheduled jobs that touch sensitive data, such as backups, key rotation and log shipping, a systemd timer is usually the better fit. The timer starts a normal service unit, so you can apply user, file system and capability limits to the job. A cron line gives you a schedule and little else.
How a timer works
The systemd.timer manual says each .timer unit needs a matching unit to activate. By default a timer called foo.timer activates foo.service. If that service is already active when the timer fires, it is not restarted. The manual also warns that services with RemainAfterExit= set are usually unsuitable for repeating timers, because they activate once and stay around.
That pairing is the security advantage. Whatever you can write in a service unit applies to the job.
What you can add to a job unit
In the matching service, set a dedicated user and the directives from the basic sandbox list: NoNewPrivileges=yes, ProtectSystem=strict, ProtectHome=yes, PrivateTmp=yes and ReadWritePaths= for the one directory the job writes. A backup job can see what it needs and nothing more. Output goes to the journal, so every run is logged with the unit name and exit status without extra plumbing.
A small example:
# backup.service
[Service]
Type=oneshot
User=backup
ProtectSystem=strict
ReadWritePaths=/var/backups/app
NoNewPrivileges=yes
ExecStart=/usr/local/bin/run-backup
# backup.timer
[Timer]
OnCalendar=daily
Persistent=true
RandomizedDelaySec=15min
[Install]
WantedBy=timers.target
Timer settings that affect reliability
- Persistent=true: the manual says the last trigger time is stored on disk and the service runs at once if a run was missed while the timer was inactive, for example when the machine was off. It only works with
OnCalendar=timers and defaults to false. - RandomizedDelaySec=: adds a random delay to spread load. Default is 0.
- AccuracySec=: defaults to one minute, so a timer fires within a window, not at an exact second. Do not use timers for tasks that need exact timing.
- Timers using
OnCalendar=wait for the clock to be set and synchronized before starting.
Missed security jobs are a quiet failure, so alert when a run has not succeeded in the expected period. See heartbeat alerts for silent monitoring failure.
Where cron still makes sense
Cron is simple, familiar and everywhere. For small tasks run as a normal user with no secrets, it is fine. Know what it does not give you. The systemd.exec manual notes that NoNewPrivileges= has no effect on processes started through tools such as at, crontab or systemd-run, so a service's sandbox does not follow a job launched that way.
Also audit the places jobs hide: user crontabs, /etc/cron.d, and timers listed by systemctl list-timers --all. A persistence mechanism for an intruder often looks like a scheduled job.
Safeguard offers asset discovery and compliance evidence automation, and a free scan at gold.safeguard.sh/scan.
Quick check
Which unit does foo.timer start?
By default foo.service, unless the Unit= setting in the timer names another unit.
What does Persistent=true do?
It stores the last trigger time on disk and runs the job at once if a scheduled run was missed while the timer was inactive. It applies to OnCalendar= timers.
Sources
Last reviewed: 19 October 2026.