1 of 3

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.

  • at
  • atq
  • atrm
  • crontab
  • crontab -e
  • /etc/cron.d
  • /etc/crontab
  • anacron

45 min5 exercises

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
PatternMeaning
*Every value
*/5Every 5th value, for example every 5 minutes
1,15A list: the 1st and 15th
1-5A range: Monday to Friday in the weekday field
@reboot, @daily, @weeklyShortcuts for common patterns

Where the job lives decides who runs it:

LocationRun asNotes
crontab -e (per user)That userFive fields then command. Stored in /var/spool/cron/user
/etc/crontab, /etc/cron.d/*The user named in column 6Six fields: the sixth is the user
/etc/cron.hourly, cron.daily, cron.weekly, cron.monthlyrootDrop 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.

  1. Warm-up

    Run something in two minutes

    Make sure atd is 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

    atq lists one job before it runs. After two minutes ~/lab/at.log contains a date line and atq is 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
  2. 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 JOBNUMBER prints the environment followed by echo hi at the end. After atrm JOBNUMBER, atq no longer lists it.

    Hint

    at -c N shows the job script. atrm N removes 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
  3. Practice

    A daily job for one user

    As the user alex, create a cron job that runs every day at 14:23 and writes EX200 in progress to the system log with logger. Show it with crontab -l, and explain each of the five time fields in your entry.

    Check your result

    crontab -l prints 23 14 * * * logger "EX200 in progress". The fields are minute 23, hour 14, any day of month, any month, any weekday.

    Hint

    crontab -e opens 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
  4. 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 line cleanup ran to the log) to run on weekdays at 02:30, as user root, using a file in /etc/cron.d. Check the syntax rule that differs from a user crontab.

    Check your result

    /etc/cron.d/cleanup contains 30 2 * * 1-5 root /usr/local/bin/cleanup.sh. The extra field is the user name.

    Hint

    Files in /etc/cron.d have a sixth field: the user to run as. 1-5 is 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
  5. Challenge

    Find out why a job did not run

    A user reports that their cron job * * * * * date > out.txt "does nothing". Create it for alex, wait a minute, and find the evidence. Identify two common causes: where the file is written, and the minimal PATH. 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.txt appears in alex's home, not in the directory they were in. After the fix ~/lab/cron-out.txt updates 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 -