Cron vs. systemd Timers: How Linux Task Scheduling Works
Recurring jobs keep Linux systems maintained: backups, reports, cleanup, and health checks all need to run at predictable times. Two common choices are cron, the traditional scheduler that reads crontab entries, and systemd timers, which activate service units through the systemd manager. This guide explains how each works, where their behavior differs, and how to choose and configure one safely.
What Are Cron and systemd Timers?
Cron is a family of Unix-like scheduling implementations. A background daemon reads crontab files and runs commands when their time fields match. A crontab is compact and portable across many Unix-like systems, although extensions and defaults vary between implementations.
A systemd timer is a unit that tells the system or user service manager when to activate another unit, usually a .service. The timer describes when work is due; the service describes what command runs, under which account, and with which execution controls. The official systemd.timer reference documents this timer-to-unit relationship.
Both schedule work, but neither is a durable job queue. They do not automatically provide retries for a failed command, exactly-once execution, or a record of every missed interval. Those guarantees must come from the job or a workflow system designed for them.
Why Linux Has Different Scheduling Models
Simple time-based scheduling is easy to express, but production jobs need more than a timestamp. Operators need to know which account runs a task, what happens when a host is offline, where output goes, whether overlapping runs are possible, and how to troubleshoot failures.
Cron keeps the schedule close to the command. That makes it quick to set up, but environment and service relationships are usually left to the command itself. Systemd timers separate the trigger from the service definition, so dependencies, user identity, resource limits, and journal logging can be managed with the same unit system used for services.
Neither is universally better. Cron is useful for simple, portable jobs and existing crontabs. Timers are useful when a Linux host already uses systemd and the job benefits from unit-level management. On Windows, the analogous built-in scheduler is a separate platform feature; Microsoft’s Task Scheduler documentation describes its trigger-based model.
How Cron and systemd Timers Work
Cron: daemon checks crontabs
The cron daemon reads user crontabs and system-wide schedule files. A basic user crontab line has five schedule fields followed by a command:
minute hour day-of-month month day-of-week command
For example, 15 2 * * * means 02:15 each day. When both day-of-month and day-of-week are restricted, common cron implementations treat a match in either field as sufficient; verify the implementation’s manual when the distinction matters. The crontab manual also describes environment assignments, time zones, and daylight-saving-time behavior.
Jobs run non-interactively, usually with a small environment and a shell chosen by the cron implementation or crontab. A script that works in a terminal can fail under cron if it assumes a particular PATH, working directory, shell startup file, or user session. Use explicit paths and configure required environment variables.
Cron checks schedules regularly, but a job whose scheduled time passed while the machine was off is generally not replayed by cron itself. Tools such as anacron add catch-up behavior for some use cases. Wall-clock changes can also affect calendar schedules: depending on the cron implementation, a nonexistent local time may be skipped and a repeated time may run twice.
systemd: timer unit activates a service
A systemd timer is loaded and tracked by the system or per-user service manager. When its schedule elapses, the manager queues the associated unit, which defaults to the correspondingly named service. For example, nightly-backup.timer activates nightly-backup.service. If that service is already active when the timer fires, systemd does not start another copy or queue an additional instance.
Timer units support two broad schedule types:
- Calendar timers, configured with
OnCalendar=, use wall-clock time and calendar expressions such as a daily run at 02:15. - Monotonic timers, configured with directives such as
OnBootSec=orOnUnitActiveSec=, use elapsed time relative to boot, manager start, or service activity. They are not tied to a calendar date or time zone.
Persistent=true records the last trigger time and can cause a calendar timer to run once after activation if at least one scheduled occurrence was missed while the timer was inactive. It does not replay every missed occurrence, and it does not apply to monotonic timers. AccuracySec= permits coalescing timer events in a time window; RandomizedDelaySec= can spread similar jobs over a longer interval. Review the systemd time and calendar syntax when precise scheduling matters.
The flow is:
Schedule definition -> cron daemon or systemd manager detects a due time -> command or service is activated -> exit status and output are sent to the configured logging path.
With cron, schedule and command are typically one entry. With systemd, the timer controls activation while the service controls the process environment and lifecycle. Systemd’s execution settings reference describes service identity, environment, filesystem access, and other controls.
Components and Scheduling Variants
| Aspect | Cron | systemd timer |
|---|---|---|
| Schedule and command | Usually one crontab entry | Separate .timer and .service units |
| Schedule types | Calendar fields; extensions vary | Calendar and monotonic elapsed-time schedules |
| Missed run after downtime | Not generally replayed by cron itself; anacron may help | Persistent=true can trigger one catch-up run for calendar timers |
| Execution context | Crontab owner; system files may include a user field | System service defaults to root; set User= or use a user manager |
| Logging and inspection | Syslog, mail, or explicit command logging vary by system | systemctl status and the journal are integrated |
| Service dependencies and resource controls | Must be arranged separately | Unit dependencies and service settings are available |
| Availability | Common on Unix-like systems; implementation differs | Requires a systemd service manager |
Cron commonly has per-user crontabs, system crontabs such as /etc/crontab, and drop-in files such as /etc/cron.d/. Their locations and supported extensions depend on the distribution and cron implementation.
Systemd offers system timers managed by the system instance and user timers managed by a user’s systemd instance. User timers run with that user’s identity; whether the user manager runs without an active login session depends on its configuration, including lingering. A system service normally runs as root unless its unit specifies another account.
Real-World Use Cases
- Portable maintenance script: A modest cleanup job on several Unix-like hosts can use cron when its simple schedule and existing operations are already standardized.
- Managed server backup: A systemd timer can activate a dedicated service that runs as a restricted account, writes logs to the journal, and has explicit filesystem and resource settings.
- Periodic work after downtime: A calendar timer with
Persistent=truecan make a backup or maintenance task run once after a powered-off system returns. The script should still be safe to repeat. - Elapsed-time checks: A service that should run a fixed interval after boot or after its previous activation can use monotonic timer directives rather than a wall-clock schedule.
- Cross-platform operations: Teams managing both Linux and Windows can keep platform-specific configuration while applying the same operational checks for identity, logs, overlap, and recovery. See the Windows Task Scheduler guide.
Practical Guide: Define, Test, and Operate a Job
First decide whether the requirement is calendar-based or interval-based, whether a missed run should be caught up, and what should happen if a previous run is still active. Make the command idempotent where possible: a rerun should not corrupt data or duplicate side effects. Keep credentials out of crontabs and unit files; use the host’s approved secret-delivery mechanism.
A cron entry
For a user crontab, crontab -e opens the current user’s schedule. If the job runs as a dedicated backup account, provision its state directory and edit that account’s crontab. Define a predictable PATH, use absolute paths, and optionally use flock (provided by util-linux on many distributions) to prevent two copies of a long-running command:
sudo install -d -o backup -g backup -m 0700 /var/lib/nightly-backup
sudo -u backup crontab -e
Add this entry to that crontab:
SHELL=/bin/sh
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
15 2 * * * /usr/bin/flock -n /var/lib/nightly-backup/backup.lock /usr/local/sbin/backup.sh /var/lib/nightly-backup
Ensure that the account owning the crontab can write the lock and destination paths. The command’s standard output and error may be mailed, logged by the host, or explicitly redirected; choose and maintain one logging strategy rather than silently discarding errors. Inspect the result with crontab -l and the distribution’s cron logs.
A systemd service and timer
Create /etc/systemd/system/nightly-backup.service:
[Unit]
Description=Run the nightly backup
[Service]
Type=oneshot
User=backup
Group=backup
ExecStart=/usr/local/sbin/backup.sh /var/lib/nightly-backup
StateDirectory=nightly-backup
StateDirectoryMode=0700
UMask=0077
NoNewPrivileges=yes
ProtectSystem=strict
ProtectHome=read-only
PrivateTmp=yes
The service account and script must exist, and the script must be able to read its intended sources and write to the managed state directory. ProtectSystem=strict makes the filesystem read-only to the service except for explicitly managed writable paths such as StateDirectory=. Test filesystem restrictions against the real workload before relying on them.
Create /etc/systemd/system/nightly-backup.timer:
[Unit]
Description=Run the nightly backup every day
[Timer]
OnCalendar=*-*-* 02:15:00
Persistent=true
AccuracySec=5min
RandomizedDelaySec=10min
[Install]
WantedBy=timers.target
After saving both files, validate the calendar expression and units, then enable the timer:
systemd-analyze calendar '*-*-* 02:15:00'
sudo systemd-analyze verify /etc/systemd/system/nightly-backup.service /etc/systemd/system/nightly-backup.timer
sudo systemctl daemon-reload
sudo systemctl enable --now nightly-backup.timer
systemctl list-timers --all
systemctl status nightly-backup.timer
journalctl -u nightly-backup.service
daemon-reload makes the manager reread unit files; it does not run the job by itself. To test the service immediately, start it explicitly with sudo systemctl start nightly-backup.service, then inspect its exit status and logs. Avoid enabling a replacement timer while the original cron job remains active unless the job is protected against duplicate runs.
For general systemd unit relationships, read the systemd architecture guide; for ongoing service operations, see managing systemd services. Apply least privilege and review the host’s Linux server hardening practices; on AppArmor-enabled hosts, the AppArmor guide covers profile-based confinement.
Common Misconceptions
Persistent=true replays every missed run. It records whether a calendar timer was missed while inactive and can trigger one catch-up activation. It is not a backlog or retry mechanism.
A systemd timer is itself the job process. The timer schedules activation. The associated service unit defines the command and its execution context.
Cron runs commands in the same environment as an interactive terminal. Cron jobs are non-interactive and often receive a minimal environment. Explicit paths and environment settings prevent many failures.
Every timer firing starts a new copy. If the target service is already active, the timer does not spawn another instance or queue that firing. Use an explicit lock or a job system with queue semantics when overlapping or missed executions require more control.
Systemd timers are always more reliable than cron. Either scheduler can activate a job, but neither makes the job idempotent, monitors its business outcome, or automatically retries all failures. Reliability depends on schedule semantics, observability, and recovery design.

