Job Scheduling: cron, at, and systemd Timers
What You Will Achieve
- Read and write the
crontabsyntax (five fields) - Explain the difference between a user crontab and a system crontab (
/etc/crontab,/etc/cron.d) - Manage one-shot jobs with
at/atq/atrm - Decide when to use
anacronandsystemd timer - Understand execution control with
cron.allow/cron.deny - Avoid exam-frequent pitfalls such as the "cron PATH problem"
This is the core of LPIC-1 objective 107.2 "Automate system administration tasks by scheduling jobs". It is the technique to run routine work, such as regular backups and log rotation, automatically without human intervention.
Which Scheduler Should You Use?
For repeated runs choose cron, for a single run choose at, for periodic runs on a machine that is not always on choose anacron, and for advanced control in a systemd environment choose systemd timer. That is the starting point for the decision.
| Requirement | Tool | Key command / file |
|---|---|---|
| Periodic, e.g. daily or hourly | cron |
crontab -e, /etc/crontab |
| Once at a given time | at |
at, atq, atrm |
| Avoid misses when the machine was off | anacron |
/etc/anacrontab |
| systemd integration, deps, log linkage | systemd timer |
.timer + .service, OnCalendar= |
cron skips a job if the machine is not running at the scheduled time. By contrast, anacron judges by "days elapsed since the last run", so it does not miss runs even on environments like laptops that are powered off for stretches of time.
The crontab Syntax
Each crontab line consists of six elements: "minute hour day month weekday command". The first five are time fields, and the sixth onward is the command to run.
# ┌───────────── minute (0 - 59) # │ ┌───────────── hour (0 - 23) # │ │ ┌───────────── day of month (1 - 31) # │ │ │ ┌───────────── month (1 - 12) # │ │ │ │ ┌───────────── day of week (0 - 7, 0 and 7 are Sunday) # │ │ │ │ │ # * * * * * command to run
The special characters usable in each field are as follows.
| Symbol | Meaning | Example |
|---|---|---|
* |
All values | * in minute means every minute |
, |
List of values | 0,30 means minute 0 and minute 30 |
- |
Range | 1-5 (weekday) means Mon-Fri |
/ |
Interval (step) | */15 (minute) means every 15 minutes |
Let us read some concrete examples.
*/15 * * * * /usr/local/bin/check.sh every 15 minutes 0 3 * * * /usr/local/bin/backup.sh daily at 3:00 0 9 * * 1-5 /usr/local/bin/report.sh weekdays (Mon-Fri) at 9:00 0 0 1 * * /usr/local/bin/monthly.sh 1st of every month at 0:00
On Vixie-style cron you can also use nicknames such as @reboot, @daily, @hourly, @weekly, @monthly, @yearly (@annually), and @midnight. @reboot runs once when the cron daemon starts.
In the weekday field, both 0 and 7 mean Sunday. The exam often asks about the "weekday number". Remember: 0 = Sunday, 1 = Monday ... 6 = Saturday, and 7 is also Sunday.
Steps
Step 1: Edit the user crontab
crontab -e
crontab: installing new crontab
crontab -e opens an editor (set by $EDITOR / $VISUAL) and edits the current user's crontab. On save it is stored under /var/spool/cron/ (or crontabs/ depending on the distribution) and the cron daemon picks it up automatically. Adding the following line runs the backup daily at 3:00.
0 3 * * * /usr/local/bin/backup.sh
Step 2: List and remove entries
crontab -l crontab -r
0 3 * * * /usr/local/bin/backup.sh
crontab -l shows the registered entries and crontab -r removes the entire crontab. Since -r deletes without confirmation, it is safer to keep a copy with -l first. As root you can operate another user's crontab with crontab -u user -e / -u user -l.
crontab -r and crontab -e are adjacent keys; hitting -r by mistake wipes all entries. It is a good idea to back up with crontab -l > ~/crontab.bak.
Step 3: Use the system crontab
cat /etc/crontab ls /etc/cron.d/
SHELL=/bin/sh PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin # m h dom mon dow user command 17 * * * * root cd / && run-parts --report /etc/cron.hourly
/etc/crontab and files under /etc/cron.d/ are the system crontab. Unlike a user crontab, they insert a "run-as user" field between the time fields and the command (a six-field layout). This difference is exam-frequent. Placing scripts in /etc/cron.{hourly,daily,weekly,monthly} runs them at each interval via run-parts.
Step 4: Run once with at
at 22:00 tomorrow atq atrm 2
warning: commands will be executed using /bin/sh at> /usr/local/bin/deploy.sh at> <EOT> job 2 at Sat May 31 22:00:00 2026 2 Sat May 31 22:00:00 2026 a user
at runs a job once at a specified time. Enter the command at the prompt and confirm with Ctrl+D (<EOT>). Check the queue with atq (= at -l) and delete job number 2 with atrm 2 (= at -d 2). The time can be given flexibly, such as 10:00, now + 1 hour, midnight, or teatime (16:00). batch differs from at in that it runs the job when system load drops.
Step 5: Control who may schedule jobs
cat /etc/cron.allow cat /etc/cron.deny
alice bob
cron usage is controlled by /etc/cron.allow and /etc/cron.deny. The decision rules are as follows.
| State of the files | Result |
|---|---|
cron.allow exists |
Only users listed there are allowed |
No cron.allow, cron.deny exists |
All users except those in cron.deny |
| Neither file exists | Implementation-dependent (often root only) |
at is controlled the same way with /etc/at.allow / /etc/at.deny. The *.allow file takes precedence: when it exists, *.deny is not consulted.
anacron vs systemd timer
cron runs on the assumption that the power is on, but anacron and systemd timer can make up for runs missed while powered off. If the host is not an always-on server, these two are worth considering.
anacron
/etc/anacrontab has a different syntax from cron: it uses four fields, "period (days), delay (minutes), job-identifier, command".
cat /etc/anacrontab
# period delay job-identifier command 1 5 cron.daily run-parts --report /etc/cron.daily 7 25 cron.weekly run-parts --report /etc/cron.weekly @monthly 45 cron.monthly run-parts --report /etc/cron.monthly
The first field is "how many days between runs" and the second is "the delay (minutes) to wait after startup". anacron cannot specify minutes or a clock time; it only has day-level granularity. It records the last run date under /var/spool/anacron/ and, if the interval has been exceeded, runs the job at startup.
systemd timer
A systemd timer consists of two units: a .timer unit and a .service unit that does the actual work.
systemctl list-timers
NEXT LEFT LAST PASSED UNIT ACTIVATES Sat 2026-05-31 03:00:00 UTC 8h left Fri 2026-05-30 03:00:00 UTC 15h ago backup.timer backup.service
OnCalendar= inside the .timer specifies the run time. OnCalendar=*-*-* 03:00:00 means daily at 3:00, and OnCalendar=daily is equivalent. systemctl list-timers lists the next run, last run, and the associated service. The strength of systemd timer is that it handles dependency control, log inspection via journalctl, and catch-up runs (like anacron) with Persistent=true.
You can verify that an OnCalendar= expression is valid with systemd-analyze calendar "Mon *-*-* 09:00:00". It prints the next trigger time.
Common Mistakes and Fixes
Symptom: A script that "should" run under cron does not (PATH problem)
Cause: The PATH of the shell cron starts is shorter than an interactive login shell, and /etc/profile and .bashrc are not sourced. The command is not found and fails
Check:
* * * * * env > /tmp/cron-env.txt
Fix: Write commands with an absolute path (/usr/local/bin/backup, not backup). Or set PATH=... explicitly at the top of the crontab.
Symptom: Environment variables in the script are unset and it misbehaves
Cause: cron does not inherit the interactive shell's environment variables (custom settings around LANG, HOME, etc.). A script that assumes a login shell behaves unexpectedly
Check:
crontab -l
Fix: Set the needed variables explicitly in the script, or write LANG=ja_JP.UTF-8 and the like in the crontab. To reproduce a login environment, start it with bash -lc 'command'.
Symptom: Everything after % in the crontab is ignored / the command is cut off
Cause: In a crontab, a % on the command line is treated specially as a newline (a separator for standard input)
Check:
crontab -l
Fix: To pass a literal %, escape it with a backslash as \%. Write it like date +\%Y\%m\%d.
Symptom: A job specifying both weekday and day-of-month runs on unexpected days
Cause: In cron, when both "day of month" and "day of week" are set to something other than *, the job runs on a day where either matches (OR, not AND)
Check:
crontab -l
Fix: Understand that OR behavior is by design. For an AND condition like "a specific weekday and a specific date", check the date inside the command and control it there.
Symptom: cron output (errors) is invisible
Cause: cron mails a job's standard output and standard error locally to the user. You will not notice unless you read that mail
Check:
grep CRON /var/log/syslog
Fix: Redirect output to a file (>> /var/log/myjob.log 2>&1). Writing MAILTO=address in the crontab changes the recipient, and MAILTO="" suppresses mail.
Completion Checklist
- [ ] Edited the user crontab with
crontab -eand verified withcrontab -l - [ ] Confirmed the system crontab (
/etc/crontab) has a user field - [ ] Registered a job with
atand managed it withatq/atrm - [ ] Understood the precedence of
/etc/cron.allow/cron.deny - [ ] Wrote scripts with absolute paths to avoid the PATH problem
- [ ] Checked systemd timers with
systemctl list-timers
Summary
| Scenario | Command / file | Purpose |
|---|---|---|
| Periodic (user) | crontab -e |
Register your own periodic jobs |
| Periodic (system) | /etc/crontab, /etc/cron.d |
Periodic jobs with a user field |
| Once | at, atq, atrm |
One-shot run at a given time |
| Miss prevention | /etc/anacrontab |
Daily work on non-always-on hosts |
| systemd integration | .timer + OnCalendar= |
Deps, logs, Persistent runs |
| Execution control | cron.allow / cron.deny |
Per-user allow / deny |
Job scheduling is the foundation for automating system operations. Combined with log management and process priority, it raises the reliability of unattended operation by another level.
Next Reading
- System Logging: journald and rsyslog
- Process Priorities: How nice and renice Work
- Shell Environment Variables