Cron vs systemd Timers

Automate tasks with cron and systemd timers — Linux · networking · telemetry.

Focus: automate tasks with cron and systemd timers

Sponsored

You've just deployed a script that checks disk usage, but you can't trust yourself to run it every morning at 2 AM — and neither can your team. Manually launching maintenance scripts, report generators, or health checks is a recipe for missed deadlines and silent failures. That's why automating tasks with cron and systemd timers is a foundational skill for any Linux administrator or backend developer: it turns repetitive chores into self-sustaining services.

The problem this lesson solves

Routine system maintenance — log rotation, backup creation, cache purging, telemetry collection — frequently gets skipped when it depends on a human remembering to run a command. Worse, ad-hoc runs happen at unpredictable times, making output unreliable and debugging a nightmare. You need a dependable, scheduled execution mechanism that runs even when you're asleep, and that persists across reboots.

Cron has been the default for decades, but modern systemd-based distributions offer a more robust alternative: systemd timers. Both solve the same core problem — running a command at a defined time — but they differ radically in reliability, logging, and dependency handling. This lesson arms you with both tools so you can choose the right one for your use case and avoid the classic pitfalls that cause silent failures.

Core concept / mental model

Think of a job scheduler as a metronome for your system: it ticks at set intervals, triggering a repeated action. Cron is the old metronome — simple, JSON-like configuration, but no awareness of whether the previous tick actually finished. systemd timers are the new metronome — they integrate with the system's init process, so they know about dependencies, resource states, and can wake the machine from sleep to run a job.

  • Cron reads a crontab file (per-user or system-wide) containing lines with five time fields (minute, hour, day of month, month, day of week) followed by a command. It's perfect for simple, interval-based schedules like “every day at 6 AM.”
  • systemd timers are unit files (.timer) that pair with a service unit (.service). They offer calendar events (e.g., Mon..Fri 02:00:00), monotonic timers (e.g., OnBootSec=10min), and persistence across reboots (Persistent=true). They also capture output in the journal, making debugging far easier.

Pro tip: When a Cron job fails, you often get a cryptic email or nothing at all. systemd timers log stdout/stderr to the journal — type journalctl -u myjob.service to see exactly what happened.

How it works step by step

Understanding cron syntax

A crontab line has five fields, in order:

minute (0-59) hour (0-23) day-of-month (1-31) month (1-12) day-of-week (0-7, 0 and 7 = Sunday)

For example, 30 2 * * * /usr/local/bin/backup.sh runs backup.sh every day at 2:30 AM. Special strings like @daily or @reboot are shortcuts for common schedules.

systemd timer anatomy

A timer unit (e.g., /etc/systemd/system/backup.timer) declares when to trigger; a matching service unit (e.g., backup.service) defines what to run. Here's the key: the service unit should be oneshot (runs a single command) and does not need its own scheduling logic.

Step-by-step for cron

  1. Edit your crontab: run crontab -e (edit) or crontab -l (list) for your user. System-wide jobs go in /etc/crontab or /etc/cron.d/.
  2. Add a line with the five fields and the absolute path to your script.
  3. Save and exit — cron automatically picks up the change.
  4. Verify by checking /var/log/syslog (or mail) for cron logs.

Step-by-step for systemd timers

  1. Create a service unit that runs your script once.
  2. Create a timer unit that describes the schedule.
  3. Enable the timer (not the service!): systemctl enable --now backup.timer.
  4. Check status: systemctl status backup.timer and systemctl list-timers.

Hands-on walkthrough

Let's build both a cron and a systemd timer for the same task: a script that pings a remote host and logs the latency to a file. This is a classic telemetry pattern in the Linux · networking · telemetry track.

Step 0: Create the monitoring script

Create /usr/local/bin/ping_log.sh with the following content:

#!/bin/bash
# Simple ping latency logger
echo "$(date +'%Y-%m-%d %H:%M:%S') $(ping -c 1 example.com | grep time= | awk -F'time=' '{print $2}' | cut -d' ' -f1) ms" >> /var/log/ping_latency.log

Make it executable:

chmod +x /usr/local/bin/ping_log.sh

Step 1: Cron setup

# Edit your crontab
crontab -e
# Add this line to run every 5 minutes
*/5 * * * * /usr/local/bin/ping_log.sh

Wait a few minutes, then check the log:

cat /var/log/ping_latency.log
# Expected output (approx)
# 2025-05-01 10:05:00 12.3 ms
# 2025-05-01 10:10:00 14.1 ms

Step 2: systemd timer setup

Create /etc/systemd/system/ping-log.service:

[Unit]
Description=Log ping latency to file

[Service]
Type=oneshot
ExecStart=/usr/local/bin/ping_log.sh

Then create /etc/systemd/system/ping-log.timer:

[Unit]
Description=Run ping log every 5 minutes

[Timer]
OnCalendar=*:0/5
Persistent=true

[Install]
WantedBy=timers.target

Now enable the timer:

sudo systemctl daemon-reload
sudo systemctl enable --now ping-log.timer
sudo systemctl list-timers | grep ping-log

To test the service manually without waiting for the timer:

sudo systemctl start ping-log.service
cat /var/log/ping_latency.log

Pro tip: Use systemctl list-timers --all to see when each timer will fire next. This is invaluable for debugging schedule logic.

Compare options / when to choose what

Feature Cron systemd Timers
Syntax Simple five-field crontab Calendar expressions or monotonic
Missing runs after downtime No (unless with cronw or external tools) Yes with Persistent=true
Output logging Mails to user (often unread) Journal (systemd)
Dependencies None Systemd services, units
Environment variable control Limited to crontab environment Full unit environment
Random delay to spread load Yes (e.g., @reboot + sleep) Yes (RandomizedDelaySec)
Resource sharing No Can share cgroups,

This means you can't run two timers together easily

Choose cron when: you need a quick, portable schedule with no complex dependencies and you're okay with minimal logging.

Choose systemd timers when: you need persistence across reboots, dependency-based triggering, rich logging, or integration with the systemd ecosystem (e.g., start after network-online.target).

Also consider tool-specific variations:

  • cronw / cronie: enhance cron with features like @reboot and job persistence.
  • anacron: runs missed jobs after the machine is powered on (useful for daily rotation on desktops/laptops).
  • at: for one-off scheduled tasks, not recurring.

Troubleshooting & edge cases

Cron job not running

  • Check crontab syntax: run crontab -l and validate with crontab -T (on some systems) or use an online checker. A stray space or wrong wildcard can silently disable a line.
  • Check the environment: cron uses a minimal PATH (often just /usr/bin:/bin). Always use absolute paths for commands and scripts, and set PATH in the crontab if needed.
  • Look for cron logs: check /var/log/syslog or /var/log/cron for errors like "No MTA installed" — this often means cron couldn't send output and the job might have failed.

systemd timer not firing

  • Did you enable the timer, not the service? Running systemctl enable ping-log.service won't schedule anything; you need to enable the .timer unit.
  • Check timer status: systemctl status ping-log.timer shows if it's active. Use systemctl list-timers | grep ping-log to see the next run.
  • Is the service unit in order? Use systemctl start ping-log.service manually to ensure it works. If the service fails, the timer will keep trying and log errors in the journal.
  • Time zone issues: systemd timers use the local time zone by default; cron uses the system's time zone. If you're in a container or across time zones, be explicit.
  • Persistent=true and random delay: If you set Persistent=true, missed runs trigger immediately after boot. Combine with RandomizedDelaySec=5min to avoid a boot-time burst.

Script output missing

  • For cron, redirect output to a file or use MAILTO to receive errors: 30 2 * * * /script.sh >> /var/log/script.log 2>&1. For systemd, check journalctl -u ping-log.service — it captures both stdout and stderr by default.

Avoiding duplicate runs

  • If a job takes longer than the interval, use a lock file or flock to prevent overlapping runs. Cron and systemd timers both start a new instance regardless of the previous one. Example: * * * * * flock -n /tmp/script.lock /script.sh.

What you learned & what's next

You now understand how to automate tasks with cron and systemd timers — from the core syntax and mental model to hands-on implementation and troubleshooting. You can compare the two approaches and choose the right one based on your needs: quick and simple cron, or robust and observable systemd timers.

You've achieved the learning objectives:

  • Explain the core idea behind automating tasks with cron and systemd timers.
  • Complete a practical exercise (ping logger) using both methods.

Your next step in the Linux · networking · telemetry track is to explore network service monitoring — how to actively poll endpoints and collect latency data over time. Automating tasks is the backbone; monitoring what those tasks produce is the next frontier. Keep building!

Practice recap

Now try it yourself: create a script that logs memory usage every 10 minutes using both cron and a systemd timer. Compare the outputs and notice how systemd timers give you richer logs in the journal. Next, set Persistent=true and see how missed runs are handled after a reboot — this will solidify the mental model of systemd timers as the modern choice for critical automation.

Common mistakes

  • Forgetting to make the script executable: cron and systemd will fail silently if the script lacks the execute bit.
  • Using relative paths inside the script — cron has a minimal PATH, so always use absolute paths or set PATH in the crontab. For systemd, set WorkingDirectory in the service unit.
  • Forgetting to enable the systemd timer — you must run systemctl enable --now timer.service, not just the service unit.
  • Ignoring overlap: if a job takes longer than the schedule interval, you risk multiple concurrent runs — use flock or a lock file.

Variations

  1. Use anacron to run jobs that were missed due to system downtime (e.g., on laptops).
  2. Use cronie or cronw for enhanced cron features like @reboot support and better logging.
  3. Use at for one-off scheduled tasks instead of recurring schedules.

Real-world use cases

  • Automatically rotate and compress application log files every night using a systemd timer to avoid filling the disk.
  • Schedule a nightly cron job to back up a database and upload the dump to an S3 bucket.
  • Run a health check script that pings internal services every 5 minutes and alerts on failure via a webhook.

Key takeaways

  • Understand the five-field cron syntax and special strings like @daily and @reboot.
  • Recognize the added reliability of systemd timers: persistence, dependency management, and built-in journal logging.
  • Know when to choose cron (simplicity, portability) vs. systemd timers (robustness, integration).
  • Always test scripts manually before scheduling, and use absolute paths to avoid environment pitfalls.
  • Use systemctl list-timers and journalctl for observability — they turn scheduling from black-box into debuggable.
  • Prevent overlapping runs with lock files or flock to avoid race conditions in long-running jobs.

Sponsored

Sponsored

Discussion

Questions, corrections, and tips help everyone reading this page.

0 comments

Add a comment

Shown publicly with your comment.

Be constructive · max 4,000 characters

No comments yet — start the thread.

Related tutorials, quizzes, and articles for this topic.