DevTools

Cheatsheet Cron Jobs

Agendamento de tarefas em sistemas Unix/Linux

Back to languages
Cron Jobs
23 cards found
Categories:
Versions:

Syntax and Fields


5 cards
Base structure
# ┌──────── minute (0-59)
# │ ┌────── hour (0-23)
# │ │ ┌──── day of month (1-31)
# │ │ │ ┌── month (1-12)
# │ │ │ │ ┌─ day of week (0-7)
# │ │ │ │ │
# * * * * * command-to-run

# 0 and 7 = Sunday

Each * represents a time field. The 5 fields define WHEN to run. The rest is the command. The order is fixed: minute, hour, day, month, weekday. 0 and 7 both mean Sunday.

Day of week field
# Day of week (0-7, 0 and 7 = Sunday)
0 9 * * 1      # Monday at 09:00
0 9 * * 1-5    # Mon to Fri at 09:00
0 0 * * 0      # Sunday at midnight
0 9 * * MON    # = 1 (names: SUN-SAT)
0 9 * * 6,0    # Saturday and Sunday

# NOTE: if day-of-month AND day-of-week
# are restricted, it runs on EITHER (OR)

The fifth field is the day of week (0-7). It accepts names: SUN, MON, TUE... If both day-of-month and day-of-week are restricted (not *), cron runs when EITHER matches (OR logic, not AND).

Minute field
# Minute (0-59)
0 * * * *    # at the start of every hour
30 * * * *   # every half hour
*/5 * * * *  # every 5 minutes
15,45 * * * * # at minutes 15 and 45
0-30 * * * * # minutes 0 to 30 (each min)

The first field controls the minute (0-59). 0 runs at the start of the hour. */5 runs every 5 minutes. It is the most used field for frequent tasks. Valid values: 0 to 59.

Hour field
# Hour (0-23, 24h format)
0 3 * * *      # at 03:00
0 15 * * *     # at 15:00
0 9,12,18 * * * # at 09h, 12h and 18h
0 9-17 * * *   # hourly, 09h to 17h
0 */6 * * *    # every 6 hours (0h,6h,12h,18h)

The second field sets the hour (0-23). 24h format without AM/PM. 9-17 covers working hours. */6 runs every 6 hours. Combine with minute 0 for exact hours.

Day and month fields
# Day of month (1-31)
0 0 1 * *      # 1st of every month
0 0 15 * *     # 15th of every month
0 0 1,15 * *   # days 1 and 15

# Month (1-12 or JAN-DEC)
0 0 1 1 *      # January 1st
0 0 1 6,12 *   # June and December
0 0 * 1-3 *    # Jan, Feb and Sea (daily)

The third field is the day of month (1-31). The fourth is the month (1-12 or names JAN-DEC). If day is *, it runs every day. Careful: day 31 does not exist in every month.

Operators and Values


5 cards
Asterisk (all)
* * * * *    # every minute (all values)
0 * * * *    # minute 0 of EVERY hour
0 0 * * *    # midnight of EVERY day
0 0 1 * *    # day 1 of EVERY month

# * = "any value" for that field
# Equals the full range:
# * in minute = 0-59
# * in hour   = 0-23

The * means "all possible values" for the field. In the minute it equals 0-59, in the hour 0-23. It is the default value. Use it when you don't want to restrict that specific field.

Special shortcuts
@reboot     # at system startup
@yearly     # = 0 0 1 1 *   (Jan 1, 00:00)
@annually   # = @yearly
@monthly    # = 0 0 1 * *   (day 1, 00:00)
@weekly     # = 0 0 * * 0   (Sunday, 00:00)
@daily      # = 0 0 * * *   (midnight)
@midnight   # = @daily
@hourly     # = 0 * * * *   (start of the hour)

# More readable than the numeric syntax

The shortcuts @daily, @hourly etc. replace numeric expressions. @reboot runs once at system boot. They are more readable and less error-prone. Not all systems support every shortcut — check the documentation.

Comma (list)
# List of comma-separated values
0 9,12,15 * * *    # at 09h, 12h and 15h
0 0 1,15 * *       # days 1 and 15
0 9 * * 1,3,5      # Mon, Wed and Fri
0 0 * 1,4,7,10 *   # Jan, Apr, Jul, Oct

# You can combine with ranges:
0 9-12,14-17 * * * # 9h-12h and 14h-17h

The comma creates a list of discrete values. 9,12,15 runs at those 3 hours. You can mix with ranges: 9-12,14-17. No spaces after the comma. Ideal for multiple specific schedules.

Hyphen (range)
# Continuous range of values
0 9-17 * * *     # hourly, 09h to 17h
0 0 * * 1-5      # Monday to Friday
*/10 8-18 * * *  # every 10min, 08h to 18h
0 0 1-15 * *     # first 15 days of the month

# The range is inclusive:
# 9-17 = 9,10,11,12,13,14,15,16,17

The hyphen defines a continuous, inclusive range. 9-17 includes 9, 10, 11... up to 17. 1-5 in the day of week = Mon to Fri. Combine with / for steps within the range: 8-18/2 = even hours.

Slash (step)
# Step: runs every N units
*/5 * * * *      # every 5 minutes
*/15 * * * *     # every 15 minutes
0 */2 * * *      # every 2 hours
0 */6 * * *      # every 6 hours
0 9-17/2 * * *   # 9h, 11h, 13h, 15h, 17h

# */N = "every N" starting from the minimum
# Equals: 0-N/max with step N

The slash defines the step (frequency). */5 = every 5 units. */15 in the minute = 0, 15, 30, 45. You can combine with a range: 9-17/2 = odd hours between 9 and 17. Widely used for periodic tasks.

Practical Examples


5 cards
Daily backups
# Database backup at 02:30 every day
30 2 * * * /usr/bin/mysqldump -u root my_bd > /backups/bd_$(date +\%F).sql

# File backup at 03:00
0 3 * * * tar -czf /backups/site_$(date +\%F).tar.gz /var/www

# Remove backups older than 30 days
0 4 * * * find /backups -mtime +30 -delete

Backups are the most common use of cron. $(date +\%F) generates a name with the date (escape the % with \). Combine backup + automatic cleanup. Schedule during low-traffic hours (early morning).

Scripts with PHP and Node
# PHP with absolute path
0 3 * * * /usr/bin/php /var/www/app/cron.php

# Laravel Artisan
* * * * * cd /app && php artisan schedule:run

# Node.js
*/10 * * * * /usr/bin/node /scripts/worker.js

# Python
0 */4 * * * /usr/bin/python3 /scripts/etl.py

# Always use ABSOLUTE paths

Always use the absolute path of the interpreter (/usr/bin/php). Cron does not load the user's PATH. For Laravel, the standard is schedule:run every minute. Use cd if the script depends on the working directory.

Frequent tasks
# Every 5 minutes: check the queue
*/5 * * * * php /app/artisan queue:work --stop-when-empty

# Every hour: clear the cache
0 * * * * php /app/artisan cache:clear

# Every 15 minutes: data sync
*/15 * * * * /app/scripts/sync.sh

# Every minute: monitoring
* * * * * curl -s https://my-site.com/health

Recurring tasks use */N for frequency. */5 = 5 minutes, */15 = 15 minutes. For Laravel, prefer the built-in schedule. curl for simple health checks. Avoid tasks every minute if possible.

Working hours
# Reports at 09:00 Mon to Fri
0 9 * * 1-5 /scripts/report_diario.sh

# Reminder at 17:30 Mon to Fri
30 17 * * 1-5 /scripts/lembrete.sh

# Session cleanup every 30min (24/7)
*/30 * * * * php /app/artisan session:gc

# Check every 2h on weekdays
0 9-18/2 * * 1-5 /scripts/check.sh

1-5 in the day of week restricts to Mon-Fri. Combine with 9-17 in the hour for working hours. 9-18/2 runs at 9h, 11h, 13h, 15h, 17h. Maintenance tasks can run 24/7 with * in the day.

Monthly and yearly
# Billing on day 1 at 06:00
0 6 1 * * /scripts/billing.sh

# Quarterly report (Jan,Apr,Jul,Oct)
0 8 1 1,4,7,10 * /scripts/quarterly.sh

# Certificate renewal (day 15)
0 3 15 * * certbot renew --quiet

# System anniversary (Jan 1)
0 0 1 1 * /scripts/anniversary.sh

Monthly tasks fix the day of month: 0 6 1 * * = day 1 at 6h. Quarterly uses a list of months: 1,4,7,10. certbot renew is a classic monthly case. For "last day of the month", use logic in the script.

Management and CLI


4 cards
crontab commands
crontab -e     # edit the user's crontab
crontab -l     # list current entries
crontab -r     # remove ALL entries
crontab -u user -e  # edit another user's

# Edit with a specific editor:
EDITOR=nano crontab -e
EDITOR=vim crontab -e

crontab -e opens the editor to manage jobs. -l lists without editing. -r deletes everything (careful!). -u for another user (requires root). The editor is defined by EDITOR or VISUAL.

System files
/etc/crontab          # system crontab
/etc/cron.d/          # package jobs
/var/spool/cron/      # user crontabs

# Auto-run directories:
/etc/cron.hourly/     # hourly scripts
/etc/cron.daily/      # daily scripts
/etc/cron.weekly/     # weekly scripts
/etc/cron.monthly/    # monthly scripts

# Just drop an executable script there

/etc/crontab is the global system file. /etc/cron.d/ for package jobs. The cron.daily/ etc. directories run scripts automatically — just copy an executable file. Simpler than editing crontab for standard tasks.

Permissions and control
# Allow users:
/etc/cron.allow    # whitelist (only these)
/etc/cron.deny     # blacklist (all except)

# Check the service:
systemctl status cron
systemctl restart cron
systemctl enable cron    # start on boot

# Logs (Ubuntu/Debian):
grep CRON /var/log/syslog

cron.allow and cron.deny control who can use cron. If cron.allow exists, only those listed can. The service is called cron (Debian) or crond (RHEL). Logs in /var/log/syslog filtering by CRON.

Redirect output
# Save output to a file:
0 3 * * * /script.sh >> /var/log/job.log 2>&1

# Discard output (silence):
0 3 * * * /script.sh > /dev/null 2>&1

# Save only errors:
0 3 * * * /script.sh 2>> /var/log/errors.log

# Send by email (if MAILTO is set):
MAILTO=admin@example.com
0 3 * * * /script.sh

By default, cron emails the output. >> /file 2>&1 saves stdout and stderr. > /dev/null 2>&1 silences everything. 2>> captures only errors. MAILTO sets the email recipient. Always redirect to avoid unwanted emails.

Advanced and Good Practices


4 cards
Environment variables
# Define at the top of the crontab:
SHELL=/bin/bash
PATH=/usr/local/bin:/usr/bin:/bin
MAILTO=admin@example.com
HOME=/home/deploy

# Or inline in the command:
0 3 * * * DB_PASS=secret /script.sh

# Load env from a file:
0 3 * * * . /home/user/.env && /script.sh

Cron has a minimal PATH — set it explicitly. SHELL defines the interpreter. MAILTO controls emails. Use . /file.env to load variables. Without this, commands like node or composer are not found.

Avoid overlap
# flock: prevents simultaneous runs
*/5 * * * * flock -n /tmp/job.lock /script.sh

# Alternative with a PID file:
* * * * * [ ! -f /tmp/job.pid ] && /script.sh & echo $! > /tmp/job.pid

# systemd timer (modern alternative):
# /etc/systemd/system/job.timer
# [Timer]
# OnCalendar=*:0/5

flock -n creates a lock file — if it is already running, it does not execute. Essential for tasks that take longer than the interval. Without a lock, multiple instances can corrupt data. systemd timers are the modern alternative with better control.

Debugging and logs
# Check if cron is active:
systemctl status cron
pgrep cron

# Logs in real time:
tail -f /var/log/syslog | grep CRON

# Test the expression before scheduling:
# Use: crontab.guru (online)

# Test script with a timestamp:
* * * * * echo "$(date): ran" >> /tmp/cron_test.log

# Remove after confirming:
# crontab -e → delete the test line

tail -f /var/log/syslog | grep CRON shows runs in real time. crontab.guru explains expressions visually. For debugging, schedule * * * * * with echo and check the log. Common problems: wrong PATH and permissions.

Best practices
# 1. ALWAYS absolute paths
0 3 * * * /usr/bin/php /app/script.php

# 2. Redirect output
0 3 * * * /script.sh >> /var/log/job.log 2>&1

# 3. Use flock for long tasks
*/5 * * * * flock -n /tmp/j.lock /script.sh

# 4. Comment each entry
# Daily database backup
30 2 * * * /scripts/backup.sh

# 5. Test the command manually first

Always use absolute paths — cron does not inherit the PATH. Redirect output to logs. Use flock to avoid overlap. Comment each job for future maintenance. Test the command manually before scheduling. Prefer scripts over long inline commands.