, ,

Schedule Tasks in Linux With cron and systemd Timers (Linux for Beginners, Part 15)

cron runs the jobs that keep Linux servers going. Learn crontab syntax, the PATH trap that kills jobs silently, and when a systemd timer does it better.

Linux for Beginners · Part 15 of 24

Your backup script runs perfectly when you type its name. You add it to cron, walk away, and the next morning there is no backup. The logs look clean. The script itself is fine. What broke is the environment cron handed the job, and it is the most common reason a scheduled task fails while the same command works in your shell. This part fixes that trap and shows you both schedulers a Linux admin reaches for.

Bottom line. cron runs a command on a fixed schedule set by five time fields. It does not read your shell profile, so a job that works when you type it can still fail on the clock. When you need logging, catch-up for missed runs, or ordering between jobs, a systemd timer is the better tool. Both are here with real commands and their output.

Written for two readers at once: the college passout meeting crontab for the first time, and the working admin who wants the timer version and the PATH fix without scrolling. If you are following on a real machine, any Ubuntu, Debian, Rocky, or Alma box will do.

One crontab line, decoded

A cron entry is five time fields followed by the command to run. Read them left to right: minute, hour, day of month, month, day of week. An asterisk in a field means every value. The diagram below maps a real line.

The five crontab fields302**1backup.shminutehourday of monthmonthday of weekcommand0 to 590 to 231 to 311 to 120 to 7Reads as: run backup.sh at 02:30 every Monday.Day of week 0 and 7 both mean Sunday, so pick one and stay consistent.

Your three everyday commands

Each user has a personal crontab. You never edit it by hand in a random file; you use the crontab command, which validates the syntax before saving.

$ crontab -l          # list your jobs
$ crontab -e          # edit your jobs (opens your editor)
$ crontab -r          # remove ALL your jobs, no confirmation

Look at those last two. -e and -r sit one key apart, and -r wipes your whole crontab with no prompt. I have watched an engineer clear a production crontab this way at 2 a.m. Get in the habit of typing crontab -e slowly, and keep a copy of important jobs in a file under version control so a slip is a restore, not an incident.

A few real schedules

# m h dom mon dow  command
30 2 * * 1    /usr/local/bin/backup.sh        # 02:30 every Monday
0 */4 * * *   /usr/local/bin/sync-cache.sh    # top of every 4th hour
*/15 * * * *  /usr/local/bin/healthcheck.sh   # every 15 minutes
0 9 1 * *     /usr/local/bin/invoice.sh        # 09:00 on the 1st

The step syntax */4 and */15 is the piece people miss. It means every 4th hour and every 15th minute, not from now but aligned to the clock, so */15 fires at :00, :15, :30, and :45. If you want a job every 90 minutes, cron cannot express that in one field, and that limit is a real reason people move to timers.

Shortcut strings

StringSame asWhen it runs
@rebootnoneOnce, shortly after boot
@hourly0 * * * *Top of every hour
@daily0 0 * * *Midnight every day
@weekly0 0 * * 0Sunday at midnight
@monthly0 0 1 * *1st of the month
@yearly0 0 1 1 *Jan 1 at midnight

One caution on @reboot: it fires early in boot, sometimes before networking or a database is ready. If your job needs those, add a short sleep 30 at the start of the script or, better, express the dependency with a systemd timer that waits for the network. A raw @reboot job that assumes the whole system is up will bite you the day a host reboots under load.

Where scheduled jobs actually live

Your personal crontab is one place, but a Linux box schedules work from several. Knowing which is which saves you an hour of hunting when a job runs and you cannot find where it is defined.

User crontabs live under /var/spool/cron and are edited with crontab -e. System crontabs sit in /etc/crontab and /etc/cron.d/, and these have one extra field: a user name between the schedule and the command, because the system needs to know who to run the job as. The directories /etc/cron.daily, cron.hourly, cron.weekly, and cron.monthly hold plain scripts that run on that cadence through a helper called run-parts.

On a laptop or any box that is not on around the clock, anacron covers the daily, weekly, and monthly jobs. Plain cron skips anything scheduled for a time the machine was asleep. anacron checks timestamps at boot and runs what was missed. If your nightly cleanup never happens on a machine you shut down each evening, this is usually why.

The environment trap that eats most cron jobs

This is the part worth slowing down for, because it accounts for more broken cron jobs than every syntax mistake combined. cron does not load your .bashrc or .profile. It runs your command with a bare environment, and its PATH is set to just /usr/bin:/bin. Anything you installed in /usr/local/bin or a language version manager is invisible.

Here is the failure end to end. The tool works in your shell:

$ which myreport
/usr/local/bin/myreport

# crontab line
*/5 * * * * myreport

Five minutes pass and nothing appears. The job did run. Check the cron log, which is /var/log/syslog on Debian and Ubuntu, or /var/log/cron on RHEL family systems where the service is named crond:

$ grep CRON /var/log/syslog | tail -2
Jul  3 09:25:01 web01 CRON[20431]: (deploy) CMD (myreport)
Jul  3 09:25:01 web01 CRON[20430]: (CRON) info (No MTA installed, discarding output)

cron says it ran the command and then threw the output away because no mail agent is installed. That second line is the clue most people skim past. The error went to the discarded output. Capture it by redirecting both streams to a file:

*/5 * * * * myreport >> /home/deploy/report.log 2>&1

Now the log tells the truth:

$ cat /home/deploy/report.log
/bin/sh: 1: myreport: command not found

There it is. cron could not find myreport because /usr/local/bin is not on its PATH. Two fixes, and I prefer the first:

# option 1: use the absolute path, no guessing
*/5 * * * * /usr/local/bin/myreport >> /home/deploy/report.log 2>&1

# option 2: set PATH once at the top of the crontab
PATH=/usr/local/bin:/usr/bin:/bin
*/5 * * * * myreport >> /home/deploy/report.log 2>&1

Absolute paths in cron jobs are not paranoia, they are correctness. The command you type has your full PATH behind it. The command cron runs does not. Write every cron command as if PATH were nearly empty, because it nearly is.

Where this bites in production
A job that reads a relative path is the same trap in a different coat. cron starts your job in the user home directory, not wherever you happened to be standing. A script that opens config/settings.ini works from your project folder and fails from cron. Use absolute paths for files too, or set cd /path/to/project at the start of the script. PATH and working directory are the two assumptions your interactive shell makes for free and cron does not.

systemd timers, the modern alternative

On any system running systemd, and that is nearly every current distribution, a timer can do everything cron does and a few things cron cannot. A timer is two small files: a .service that says what to run, and a .timer that says when. The timer starts the service; the service runs your command.

How a systemd timer runs your jobbackup.timerOnCalendar firesbackup.serviceType oneshotbackup.shExecStartsystemctl list-timers shows the next and last run for every timer.

Create the service unit at /etc/systemd/system/backup.service:

[Unit]
Description=Nightly site backup

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

Then the timer at /etc/systemd/system/backup.timer:

[Unit]
Description=Run the site backup at 02:30

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

[Install]
WantedBy=timers.target

That OnCalendar value follows a simple shape: DayOfWeek Year-Month-Day Hour:Minute:Second, with an asterisk for any field. Enable and start the timer, then confirm it is scheduled:

$ sudo systemctl daemon-reload
$ sudo systemctl enable --now backup.timer
$ systemctl list-timers backup.timer
NEXT                        LEFT      LAST  PASSED  UNIT          ACTIVATES
Sat 2026-07-04 02:30:00 UTC 16h left  -     -       backup.timer  backup.service

Two features earn their keep here. Persistent=true means if the machine was off at 02:30, the job runs at the next boot instead of vanishing, the same gap anacron fills for cron. And you can test a schedule before you trust it, something cron never offered:

$ systemd-analyze calendar 'Mon *-*-* 02:30:00'
  Original form: Mon *-*-* 02:30:00
Normalized form: Mon *-*-* 02:30:00
    Next elapse: Mon 2026-07-06 02:30:00 UTC

Logs come for free too. Every run is captured by the journal, so journalctl -u backup.service shows exactly what the job printed and whether it failed, with no output redirection to remember.

Concerncronsystemd timer
SetupOne lineTwo unit files
Missed run while offSkipped, anacron helpsPersistent catches up
LoggingYou redirect outputJournal, automatic
Test the scheduleNot built insystemd-analyze calendar
Ordering and depsNoneAfter, Requires, Wants
Everyone knows itYes, decades of itLess universal

My verdict on which to use

The common advice is to reach for cron because it is simple and universal. I disagree for anything that matters. cron is the right choice for a throwaway job, a personal reminder, or a one-line task on a box you own alone. The moment a job is important enough that you would want to know it failed, a systemd timer wins, because the failure lands in the journal and the schedule is testable before it goes live. The cost is one extra file. I have spent more than one file of my life debugging a silent cron job that a timer would have shown me in journalctl on the first look. Use cron for the trivial, timers for anything you would get paged about.

Which scheduler fits the jobWhat does the job need?Simple, server always onuse cronNeeds logs, catch up, orderuse a systemd timer
Interview question you will actually get
A job in cron runs fine when I run the script by hand but does nothing on schedule. Walk me through it. The answer they want: cron does not load your shell profile, so PATH is minimal and the working directory is your home. Check the cron log to confirm it fired, redirect the output to a file to see the real error, then switch to absolute paths or set PATH at the top of the crontab. Bonus points for mentioning that output goes to local mail and is discarded when no mail agent is installed.
Try it yourself
Schedule a one-line job that appends the date to a file every minute, then prove it ran.

( crontab -l 2>/dev/null; echo '* * * * * /bin/date >> /tmp/cron-test.log' ) | crontab -
Wait two minutes, then run the verify command. You should see two timestamps:

cat /tmp/cron-test.log
Clean up afterward with crontab -e and delete the test line.

Questions I actually get

Why did my job run twice, an hour apart, in the spring?
Daylight saving. When clocks jump, a job scheduled in the skipped or repeated hour can run twice or not at all. For jobs that must fire exactly once, schedule them outside the 1 a.m. to 3 a.m. window, or use a systemd timer, which handles the transition more predictably.

How do I stop a cron job from overlapping itself?
If a job runs every minute but sometimes takes ninety seconds, two copies overlap. Wrap the command in flock, for example flock -n /tmp/job.lock /usr/local/bin/job.sh, so a second start exits instead of piling on. systemd timers avoid this because the service will not start again while its previous run is active.

Do I need root to schedule a job?
No. Any user can run crontab -e for jobs that run as themselves. You need root only for system crontabs in /etc/cron.d or for a systemd timer that installs under /etc/systemd/system.

Where do I read the schedule for a job someone else set up?
Check crontab -l for each user, then /etc/crontab, /etc/cron.d/, and the cron.daily family. For timers, systemctl list-timers --all shows every one, active or not.

Scheduling is where a Linux box stops needing you at the keyboard. Get the PATH habit right and pick the tool that matches how much the job matters, and your automated work will still be running the morning you forget you set it up. Next in this series: shell scripting, where these one-line jobs grow into scripts worth scheduling. Working on servers you reach remotely? The VMware for Beginners and Cloud for Beginners guides show where those boxes come from.

A checklist for a job that went missing

When a scheduled task does not do what you expected, resist the urge to rewrite the schedule. Work down this list in order, because the answer is almost always in the first three checks.

# 1. did the job even fire?
grep CRON /var/log/syslog | tail       # Debian, Ubuntu
journalctl -u cron --since today        # systemd journal

# 2. what did it print? add a log, then read it
... your command >> /tmp/job.log 2>&1

# 3. is the schedule what you think it is?
systemd-analyze calendar 'Mon *-*-* 02:30:00'

If step 1 shows the job ran but step 2 shows a command not found or a missing file, you are back in the environment trap from earlier, and the fix is an absolute path. If step 1 shows nothing at all, the schedule never matched, and step 3 tells you when it actually would. On RHEL family systems, swap cron for crond and read /var/log/cron. This three-step pass has found the cause of every stuck scheduled job I have chased, and it takes under a minute.

Linux for Beginners · Part 15 of 24
« Previous: Part 14  |  Complete Guide  |  Next: Part 16

References

crontab(5) manual page (man7.org)
Ubuntu crontab(5) manual page
systemd/Timers, Arch Wiki

About The Author


Discover more from Journal of Intelligent Infrastructure – By Dr Pranay Jha

Subscribe to get the latest posts sent to your email.

Leave a Reply

Your email address will not be published. Required fields are marked *

Architect’s Toolkit

About the Author

Dr. Pranay Jha is a Cloud and AI Consultant with 18+ years of experience in hybrid cloud, virtualization, and enterprise infrastructure transformation. He specializes in VMware technologies, multi-cloud strategy, and Generative AI solutions. He holds a PhD in Computer Applications with research focused on Cloud and AI, has published multiple research papers, and has been a VMware vExpert since 2016 and a VMUG Community Leader.

Discover more from Journal of Intelligent Infrastructure - By Dr Pranay Jha

Subscribe now to keep reading and get access to the full archive.

Continue reading