Scheduling and TimeLesson 1 of 3
One-off and recurring jobs with at and cron
Run a command later with at, and on a schedule with cron, for one user or for the whole system.
atatqatrmcrontabcrontab -e/etc/cron.d/etc/crontabanacron
After this lesson you can
- Schedule, list, inspect and cancel a one-off job with at.
- Write a crontab entry from a plain-language requirement, and test it.
- Place system jobs in /etc/cron.d and the cron.daily-style directories, and control who may use cron.
The idea
Two tools cover most scheduling. at runs a job once, later. cron runs jobs repeatedly on a calendar pattern. A third, systemd timers, is next lesson.
Both need their daemon running: atd for at (package at), and crond for cron (package cronie, usually installed and enabled).
at
You give at a time and then, on standard input, the commands. Times are flexible: at 17:30, at now + 2 minutes, at 09:00 tomorrow, at teatime. atq lists pending jobs, at -c N shows what job N will run, atrm N removes it. Output of a job is mailed to the user if a mail system exists, so redirect it to a file instead.
cron
A crontab line has five time fields and then the command:
┌───────── minute (0-59)
│ ┌─────── hour (0-23)
│ │ ┌───── day of month (1-31)
│ │ │ ┌─── month (1-12)
│ │ │ │ ┌─ day of week (0-7, Sunday is 0 or 7)
│ │ │ │ │
* * * * * command
| Pattern | Meaning |
|---|---|
* | Every value |
*/5 | Every 5th value, for example every 5 minutes |
1,15 | A list: the 1st and 15th |
1-5 | A range: Monday to Friday in the weekday field |
@reboot, @daily, @weekly | Shortcuts for common patterns |
Where the job lives decides who runs it:
| Location | Run as | Notes |
|---|---|---|
crontab -e (per user) | That user | Five fields then command. Stored in /var/spool/cron/user |
/etc/crontab, /etc/cron.d/* | The user named in column 6 | Six fields: the sixth is the user |
/etc/cron.hourly, cron.daily, cron.weekly, cron.monthly | root | Drop in an executable script, no time fields |
The cron.daily directories are run by anacron, which catches up after downtime (a laptop that was off at 03:00 still runs the daily jobs when it wakes). cron.allow and cron.deny in /etc control who may use crontab; if cron.allow exists only the listed users may.
Try it
sudo dnf install -y at
sudo systemctl enable --now atd
echo 'date >> ~/lab/at.log' | at now + 2 minutes
atq
job 1 at Tue Nov 3 11:42:00 2026
1 Tue Nov 3 11:42:00 2026 a alex
echo 'echo hi' | at 09:00 tomorrow
at -c 2 | tail -1
atrm 2
job 2 at Wed Nov 4 09:00:00 2026
echo hi
crontab -e
crontab -l
23 14 * * * logger "EX200 in progress"
sudo journalctl -u crond --no-pager | tail -1
Nov 03 14:23:01 rhel10 CROND[4410]: (alex) CMD (logger "EX200 in progress")
A system job
echo '30 2 * * 1-5 root /usr/local/bin/cleanup.sh' | sudo tee /etc/cron.d/cleanup
30 2 * * 1-5 root /usr/local/bin/cleanup.sh
Going further
Cron runs jobs with a minimal environment: PATH is short, there is no terminal, and the working directory is the user’s home. Write absolute paths for both programs and files. In a crontab a % means “newline”, so escape it as \% in a date +%F command. Set MAILTO="" at the top of a crontab to stop mail for every job. As root, crontab -u alex -l shows another user’s table.
Practice
0 of 5 done
Do these on your own system. They get harder. Check each result, then tick it off.
- Warm-up
Run something in two minutes
Make sure
atdis running. Schedule a job for two minutes from now that appends the date to~/lab/at.log. Show the queue, wait, and show the file.Check your result
atqlists one job before it runs. After two minutes~/lab/at.logcontains a date line andatqis empty.Hint
sudo dnf install -y at; sudo systemctl enable --now atd. Feed the command on standard input:echo 'date >> ~/lab/at.log' | at now + 2 minutes.Solution
sudo dnf install -y at sudo systemctl enable --now atd echo 'date >> ~/lab/at.log' | at now + 2 minutes atq - Practice
Inspect and cancel a job
Schedule a job for tomorrow at 09:00 that runs
echo hi. Display exactly what the job will run, then cancel it.Check your result
at -c JOBNUMBERprints the environment followed byecho hiat the end. Afteratrm JOBNUMBER,atqno longer lists it.Hint
at -c Nshows the job script.atrm Nremoves it.Solution
echo 'echo hi' | at 09:00 tomorrow atq at -c $(atq | awk '{print $1}' | tail -1) | tail -1 atrm $(atq | awk '{print $1}' | tail -1) atq - Practice
A daily job for one user
As the user
alex, create a cron job that runs every day at 14:23 and writesEX200 in progressto the system log withlogger. Show it withcrontab -l, and explain each of the five time fields in your entry.Check your result
crontab -lprints23 14 * * * logger "EX200 in progress". The fields are minute 23, hour 14, any day of month, any month, any weekday.Hint
crontab -eopens your own table. The order is: minute, hour, day of month, month, day of week.Solution
crontab -e # 23 14 * * * logger "EX200 in progress" crontab -l - Practice
A system job in /etc/cron.d
As root, schedule
/usr/local/bin/cleanup.sh(create it, make it executable, and have it write the linecleanup ranto the log) to run on weekdays at 02:30, as userroot, using a file in/etc/cron.d. Check the syntax rule that differs from a user crontab.Check your result
/etc/cron.d/cleanupcontains30 2 * * 1-5 root /usr/local/bin/cleanup.sh. The extra field is the user name.Hint
Files in
/etc/cron.dhave a sixth field: the user to run as.1-5is Monday to Friday.Solution
printf '#!/bin/bash\nlogger "cleanup ran"\n' | sudo tee /usr/local/bin/cleanup.sh sudo chmod 755 /usr/local/bin/cleanup.sh echo '30 2 * * 1-5 root /usr/local/bin/cleanup.sh' | sudo tee /etc/cron.d/cleanup cat /etc/cron.d/cleanup - Challenge
Find out why a job did not run
A user reports that their cron job
* * * * * date > out.txt"does nothing". Create it foralex, wait a minute, and find the evidence. Identify two common causes: where the file is written, and the minimalPATH. Then fix the job so it writes to an absolute path and calls programs by full path.Check your result
journalctl -u crond(or/var/log/cron) shows the job was started for alex.out.txtappears in alex's home, not in the directory they were in. After the fix~/lab/cron-out.txtupdates every minute.Hint
Cron starts jobs in the user's home with a short
PATH(/usr/bin:/bin). Use absolute paths for files and commands.Solution
(crontab -l 2>/dev/null; echo '* * * * * date > out.txt') | crontab - sleep 70 sudo journalctl -u crond --no-pager | tail -3 ls ~/out.txt (echo '* * * * * /usr/bin/date > /home/alex/lab/cron-out.txt') | crontab -