Scheduling with cron and at#

Concepts#

cron — Recurring Scheduled Tasks#

cron runs commands on a schedule — every minute, every day, every Monday, etc. The cron daemon (cron or crond) checks schedules once per minute.

Crontab Syntax#

Each line in a crontab defines one scheduled task:

┌───────── minute (0-59)
│ ┌─────── hour (0-23)
│ │ ┌───── day of month (1-31)
│ │ │ ┌─── month (1-12 or jan-dec)
│ │ │ │ ┌─ day of week (0-7, 0 and 7 = Sunday, or sun-sat)
│ │ │ │ │
* * * * *  command to execute

Examples#

# Every minute
* * * * *  /usr/local/bin/check-health.sh

# Every day at 3:30 AM
30 3 * * *  /home/kmiguel/backup.sh

# Every Monday at 8 AM
0 8 * * 1  echo "Week started" | mail -s "Monday" kmiguel

# Every 15 minutes
*/15 * * * *  /usr/local/bin/sync.sh

# First day of every month at midnight
0 0 1 * *  /home/kmiguel/monthly-report.sh

# Weekdays at 6 PM
0 18 * * 1-5  /home/kmiguel/eod-report.sh

# Every 6 hours
0 */6 * * *  /usr/local/bin/cleanup.sh

Special Strings#

@reboot    command     # run once at startup
@hourly    command     # 0 * * * *
@daily     command     # 0 0 * * *
@weekly    command     # 0 0 * * 0
@monthly   command     # 0 0 1 * *
@yearly    command     # 0 0 1 1 *

Managing Your Crontab#

# Edit your crontab
crontab -e

# List your crontab
crontab -l

# Remove your entire crontab (careful!)
crontab -r

# Edit another user's crontab (root only)
sudo crontab -u username -e

Cron Tips#

Output handling: By default, cron emails any output to the user. Since most systems don’t have a local mail setup, redirect output:

# Redirect stdout and stderr to a log
30 3 * * * /home/kmiguel/backup.sh >> /home/kmiguel/backup.log 2>&1

# Discard output entirely
*/5 * * * * /usr/local/bin/healthcheck.sh > /dev/null 2>&1

Environment: cron runs with a minimal environment (limited PATH, no .bashrc). If your script needs specific tools:

# Use full paths in cron commands
30 3 * * * /usr/bin/python3 /home/kmiguel/script.py

# Or set PATH in the crontab
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
30 3 * * * backup.sh

Permissions: Scripts must be executable (chmod +x script.sh).

System Cron Directories#

Besides user crontabs, the system has directories for system-wide scheduled tasks:

ls /etc/cron.d/           # custom system cron files
ls /etc/cron.daily/       # scripts run daily
ls /etc/cron.hourly/      # scripts run hourly
ls /etc/cron.weekly/      # scripts run weekly
ls /etc/cron.monthly/     # scripts run monthly

Scripts placed in cron.daily/, cron.hourly/, etc. are run by anacron (which handles machines that aren’t always on — it runs missed jobs after boot).

System cron files in /etc/cron.d/ have an extra field for the user:

# /etc/cron.d/example
*/10 * * * * root /usr/local/bin/system-check.sh
#                 ^^^^ user field

at — One-Time Scheduled Tasks#

at runs a command once at a specific time (unlike cron which repeats).

# Install (may not be pre-installed)
sudo apt install -y at
sudo systemctl enable --now atd

# Schedule a command
at 14:30
# Type commands, then Ctrl+D to submit:
echo "Reminder: meeting!" > /tmp/reminder.txt

# Various time formats
at now + 5 minutes
at now + 2 hours
at 3:00 PM
at midnight
at noon tomorrow
at 10:00 AM 2026-05-15

# One-liner
echo "/home/kmiguel/task.sh" | at now + 1 hour

# List pending jobs
atq

# View a job's commands
at -c JOB_NUMBER

# Remove a pending job
atrm JOB_NUMBER

systemd Timers#

systemd can also schedule tasks using timer units. They are more powerful than cron (support dependencies, logging via journalctl, randomized delays) but more verbose to set up.

# List active timers
systemctl list-timers

# Common system timers
systemctl list-timers --all | head -10

A systemd timer consists of two unit files: a .timer (the schedule) and a .service (the action). For most user-level tasks, cron is simpler. Timers are worth learning when you need systemd integration.

Example Timer#

# /etc/systemd/system/backup.service
[Unit]
Description=Daily backup

[Service]
Type=oneshot
ExecStart=/home/kmiguel/backup.sh
User=kmiguel

# /etc/systemd/system/backup.timer
[Unit]
Description=Run backup daily

[Timer]
OnCalendar=daily
Persistent=true

[Install]
WantedBy=timers.target
sudo systemctl enable --now backup.timer
systemctl list-timers | grep backup

cron vs at vs systemd Timers#

Feature cron at systemd Timer
Schedule type Recurring One-time Recurring or one-time
Logging Manual (redirect) Manual journalctl
Dependencies None None Full systemd integration
Missed jobs Skipped (anacron helps) Skipped Persistent=true catches up
Complexity Simple Simple More verbose

Lab#

Exercise 1: View Existing Cron Jobs#

# Your crontab
crontab -l

# System cron
ls /etc/cron.d/
ls /etc/cron.daily/

# Active timers
systemctl list-timers --no-pager | head -10

Exercise 2: Create a Cron Job#

# Add a cron job that logs the date every minute
crontab -e
# Add this line:
# * * * * * echo "$(date): cron ran" >> /tmp/crontest.log

# Wait 2-3 minutes, then check
cat /tmp/crontest.log

# Remove the test job
crontab -e
# Delete the line you added

# Clean up
rm -f /tmp/crontest.log

Exercise 3: Use at for a One-Time Task#

# Schedule a task 1 minute from now
echo 'echo "at ran at $(date)" > /tmp/attest.txt' | at now + 1 minute

# List pending jobs
atq

# Wait, then check
cat /tmp/attest.txt

# Clean up
rm -f /tmp/attest.txt

Exercise 4: Explore System Timers#

# List all timers
systemctl list-timers --all --no-pager

# Check a specific timer
systemctl status apt-daily.timer 2>/dev/null
systemctl cat apt-daily.timer 2>/dev/null

Review#

1. What does the crontab entry `30 3 * * 1` mean?

Run the command every Monday at 3:30 AM. Fields: minute=30, hour=3, day=, month=, day-of-week=1 (Monday).

2. How do you edit your crontab?

crontab -e. This opens your personal crontab in an editor. Save and exit to install the new schedule.

3. Why should you redirect output in cron jobs?

By default, cron emails any output to the user. If no mail is configured (common on desktops), the output is lost or generates errors. Redirect to a log file or /dev/null.

4. What is the difference between cron and at?

cron runs commands on a recurring schedule (every day, every hour, etc.). at runs a command once at a specific time.

5. Why might a cron job fail even though the script works manually?

Cron uses a minimal environment with a limited PATH. Commands that work in your shell may not be found. Use full paths (e.g., /usr/bin/python3) or set PATH at the top of the crontab.


Previous: find, locate, and xargs | Next: The Boot Process