Skip to content

Cron & systemd Timers: Scheduling Jobs in Linux

Two ways to schedule work on Linux, one clear rule for choosing between them, and the debugging steps for when a job silently doesn't run.

cronsystemdjournalctl
3 min readIntermediate
Lab time: 20-30 min
RR

Ranjith R

Linux SysAdmin & Cloud Engineer

Published July 1, 2026Updated July 6, 2026
Share
Section 01

Prerequisites

  • Basic shell scripting ability
  • sudo access on a Linux host running systemd (most modern distros)
Section 02

What You'll Learn

  • Write correct crontab schedule expressions, including special strings like @reboot
  • Diagnose why a cron job runs manually but silently fails on schedule
  • Create a systemd .service + .timer unit pair as a cron replacement
  • Decide when a systemd timer is the better choice over cron
Section 03

Theory

cronis a daemon that wakes once a minute, reads every user's crontab plus /etc/crontab and /etc/cron.d/*, and executes any job whose five-field schedule matches the current minute. The fields are, in order: minute, hour, day-of-month, month, day-of-week — each accepting a number, a range (1-5), a step (*/15), a list (1,15,30), or a wildcard (*).

Special strings

StringEquivalent
@rebootRun once, at system startup
@daily0 0 * * *
@hourly0 * * * *
@weekly0 0 * * 0

Why a cron job that runs manually can fail on schedule

cron executes jobs with a minimal environment — no login shell profile, a bare PATH (often just /usr/bin:/bin), and no working directory guarantee. A script that relies on a tool installed outside that PATH, or a relative file path, works fine when you run it interactively (your shell has a full environment) and fails silently under cron.

systemd timers as a modern alternative

A systemd timer unit (.timer) is paired with a service unit of the same base name (.service) that defines the actual work. Timers support calendar expressions (OnCalendar=Mon..Fri 09:00) and monotonic expressions (OnBootSec=15min), and — unlike cron — every run is logged to the journal, missed runs can be caught up with Persistent=true, and a timer can depend on other units being ready first.

Section 04

Architecture

cron polls every minute against crontab entries; systemd timers are event-driven and journal-logged

Section 05

Hands-On Lab

Part 1 — a cron job that backs up a directory nightly:

crontab -e
# Add this line — runs at 02:30 every day, full paths only:
30 2 * * * /usr/bin/tar -czf /backups/home-$(date +\%Y\%m\%d).tar.gz /home/deploy 2>> /var/log/backup-cron.log

crontab -l                    # verify it saved
grep CRON /var/log/syslog     # confirm cron actually triggered it (Debian/Ubuntu)

Part 2 — the same job as a systemd timer:

sudo tee /etc/systemd/system/home-backup.service <<'EOF'
[Unit]
Description=Nightly home directory backup

[Service]
Type=oneshot
# ExecStart isn't run through a shell, so command substitution needs an
# explicit shell — and a literal "%" must be escaped as "%%" since systemd
# treats a single "%" as the start of its own specifier syntax (e.g. %n, %i).
ExecStart=/bin/bash -c 'tar -czf /backups/home-$(date +%%Y%%m%%d).tar.gz /home/deploy'
EOF

sudo tee /etc/systemd/system/home-backup.timer <<'EOF'
[Unit]
Description=Run home-backup.service nightly at 02:30

[Timer]
OnCalendar=*-*-* 02:30:00
Persistent=true

[Install]
WantedBy=timers.target
EOF

sudo systemctl daemon-reload
sudo systemctl enable --now home-backup.timer
systemctl list-timers --all           # confirm next scheduled run
journalctl -u home-backup.service     # see every past run's output
Section 07

Best Practices

Always use full paths in cron

Never rely on cron's minimal PATH. Use full paths for both the command and any files it touches, or explicitly set PATH= at the top of the crontab.

Redirect output, don't discard it

A cron job with no output redirection has its stdout/stderr mailed to the local user by default (if a mail system is even configured) — explicitly redirect to a log file so failures are actually visible.
Section 08

Common Mistakes

Testing a script manually, then trusting cron blindly

A script that works when you run it interactively can fail under cron purely due to environment differences (PATH, HOME, missing shell profile). Always test with env -i /path/to/script.shto simulate cron's bare environment before trusting it's cron-safe.

Forgetting daemon-reload after editing a unit file

systemd caches unit files in memory. Editing /etc/systemd/system/*.timer or .service has no effect until systemctl daemon-reload— a very common "I changed it but nothing happened" trap.
Section 09

Troubleshooting

Cron job doesn't run at all: confirm the cron daemon is active (systemctl status cron or crond), check /var/log/syslog or journalctl -u cronfor a line showing the job was picked up, and verify the crontab's owner has permission to run under /etc/cron.allow/cron.deny if configured.

systemd timer shows as active but the service never fires: run systemctl list-timers and compare the NEXT column against the current time — a common cause is an OnCalendarexpression that's syntactically valid but not what you intended; validate it directly with systemd-analyze calendar "Mon..Fri 09:00".

Section 10

Interview Questions

A .service unit defines something to run (a daemon or one-shot command). A .target unit is a synchronization point that groups other units together (like multi-user.target) — it doesn't do anything itself. A .timer unit triggers an associated .service on a schedule (calendar-based or relative), acting as a more capable, loggable replacement for cron.
See all 21 Linux interview questions
Section 11

Cheat Sheet

Processes & Services

ps aux | grep nginxFind running processes matching 'nginx'
top / htopLive CPU/memory usage per process
kill -9 PIDForce-kill a process by PID
systemctl status sshdShow a service's current status
systemctl restart nginxRestart a systemd-managed service
systemctl enable --now nginxEnable a service on boot and start it now
journalctl -u nginx -fFollow live logs for a specific service
Get the full cheat sheet (with PDF download)
FAQ

Frequently Asked Questions

Prefer a systemd timer if the system already runs systemd (nearly all modern distros) — you get journal-integrated logging, dependency ordering, and catch-up runs via Persistent=true for free. cron remains simpler for a one-line job and is more portable across older or non-systemd systems.
Section 12

Summary

cron polls a five-field schedule every minute against a minimal execution environment — always use full paths and redirect output. systemd timers pair a .timer with a .service, support calendar and monotonic schedules, log every run to the journal, and can catch up missed runs — the better default on any systemd-based host.