cron system

schedule anything. forget about it. it just runs.

The crontab man page is 400 lines long. The format has five fields. Once you understand those five fields, you can schedule anything on any Linux system forever.

You have a task that needs to run every day. A backup. A log cleanup. A report. A health check. So you do it manually. Every day. You open a terminal, type the command, wait for it to finish, and move on. You’ve been doing this for weeks. Months. You’ve missed it a few times because you were busy. Or sick. Or it was Friday and you forgot because it’s Friday.

Or you set a calendar reminder. Your phone buzzes at 2 PM: “Run the backup.” You open a terminal. You type the command. You’re a human cron job. You’re a biological scheduler with a worse uptime record than a Raspberry Pi.

cron runs commands on a schedule. Every minute, every hour, every day, every Monday at 3 AM — whatever you need. It runs whether you’re logged in or not. It runs whether you’re awake or not. It doesn’t forget. It doesn’t take sick days.

Unless you’re running Windows then wtf none of this applies to you. But hey, come to the dark side, go install WSL2 and you can follow along. We’ll wait. Impatiently.

If you’re lazy like me (all sysadmins are!) then click here for the cron cheat sheet.


The cron schedule format

┌───────────── minute (0 - 59)
│ ┌───────────── hour (0 - 23)
│ │ ┌───────────── day of month (1 - 31)
│ │ │ ┌───────────── month (1 - 12)
│ │ │ │ ┌───────────── day of week (0 - 7, where 0 and 7 = Sunday)
│ │ │ │ │
* * * * * command

Five fields. That’s the whole format. An asterisk (*) means “every.” A number means “at exactly this value.”

Examples

Schedule Cron expression
Every minute * * * * *
Every hour (on the hour) 0 * * * *
Every day at midnight 0 0 * * *
Every day at 3:30 AM 30 3 * * *
Every Monday at 9 AM 0 9 * * 1
Every weekday at 6 PM 0 18 * * 1-5
First of every month at noon 0 12 1 * *
Every 15 minutes */15 * * * *
Every 6 hours 0 */6 * * *

The */N syntax means “every N.” */15 in the minute field means minutes 0, 15, 30, 45.

Ranges use dashes: 1-5 means Monday through Friday. Lists use commas: 1,3,5 means Monday, Wednesday, Friday.


Edit your crontab

crontab -e

Opens your personal crontab in your default editor. Add your scheduled commands, save, and exit. cron picks up the changes automatically — no restart needed.

View your current crontab

crontab -l

Lists everything you have scheduled. Run this before editing so you know what’s already there.

Delete your entire crontab

crontab -r

Removes all your scheduled jobs. No confirmation. Be careful. If you want to remove just one job, use crontab -e and delete the line.


Practical examples

Daily backup at 2 AM

0 2 * * * tar -czf /backup/home_$(date +\%Y\%m\%d).tar.gz /home/owner/

Creates a compressed backup of your home directory every night. The \% escapes are required because cron interprets % as a newline. This is the single most common cron gotcha.

Clean old logs every Sunday

0 4 * * 0 find /var/log -name "*.log" -mtime +30 -delete

4 AM every Sunday. Deletes log files older than 30 days. Your disk space thanks you.

Health check every 5 minutes

*/5 * * * * curl -s https://your-site.com/health > /dev/null || echo "Site down" | mail -s "Alert" you@email.com

Pings your site every 5 minutes. If it fails, sends an email. A poor man’s uptime monitor. It works.

Sync files every hour

0 * * * * rsync -az /local/data/ user@server:/remote/data/

rsync on the hour, every hour. Automated file sync without a third-party service.


Redirect output (important)

By default, cron emails output to the system’s local mail. You probably don’t have local mail configured. So output goes nowhere. Or worse, it fills up a mailbox you never check.

Send output to a log file

0 2 * * * /home/owner/backup.sh >> /var/log/backup.log 2>&1

>> appends stdout to the log. 2>&1 sends stderr there too. Now you can check the log to see if your job ran and whether it succeeded.

Discard output entirely

*/5 * * * * /home/owner/health-check.sh > /dev/null 2>&1

When you don’t care about the output and just want the job to run silently.


Use full paths (always)

cron doesn’t use your shell’s PATH. It has a minimal PATH that might not include /usr/local/bin or ~/.local/bin. Always use full paths for commands:

# Bad — might not find hugo
0 3 * * * hugo --destination /var/www/site/

# Good — full path
0 3 * * * /home/owner/.local/bin/hugo --destination /var/www/site/

Or set PATH at the top of your crontab:

PATH=/usr/local/bin:/usr/bin:/bin:/home/owner/.local/bin
0 3 * * * hugo --destination /var/www/site/

Environment variables

Set them at the top of your crontab, before any jobs:

SHELL=/bin/bash
PATH=/usr/local/bin:/usr/bin:/bin
MAILTO=you@email.com

0 2 * * * /home/owner/backup.sh

SHELL ensures your jobs run in bash (cron uses /bin/sh by default). MAILTO sends job output to your email if you have mail configured. Set MAILTO="" to disable email entirely.


System cron vs user cron

Your personal crontab (crontab -e) runs as your user. System-wide cron jobs live in:

Location Purpose
/etc/crontab System crontab (has a user field)
/etc/cron.d/ Drop-in cron files (packages add jobs here)
/etc/cron.daily/ Scripts that run daily
/etc/cron.weekly/ Scripts that run weekly
/etc/cron.monthly/ Scripts that run monthly

The cron.daily/, cron.weekly/, and cron.monthly/ directories use anacron to run scripts. Just drop a script in the folder and make it executable. No cron syntax needed.


Debugging cron

“My cron job isn’t running”

  1. Check the crontab exists: crontab -l
  2. Check the schedule: paste your expression into crontab.guru to verify
  3. Check the logs: grep CRON /var/log/syslog (or journalctl -u cron)
  4. Check the paths: use full paths for every command
  5. Check permissions: the script must be executable (chmod +x script.sh)
  6. Check the % escaping: % in cron means newline. Use \% for literal percent signs
  7. Test the command manually: run it in your terminal first to make sure it works

Check cron logs

grep CRON /var/log/syslog | tail -20

Shows the last 20 cron executions. If your job isn’t in the log, cron didn’t run it. Check the schedule.


The flags that actually matter

Command What it does
crontab -e Edit your crontab.
crontab -l List your scheduled jobs.
crontab -r Remove your entire crontab.
crontab -u USER -l List another user’s crontab (requires root).

Schedule syntax:

Symbol Meaning
* Every value
*/N Every N values
N At exactly N
N-M Range from N to M
N,M N and M specifically

“But I use—”

Don’t.

“I use Task Scheduler on Windows.” Task Scheduler has a GUI with tabs, triggers, conditions, and actions spread across six different panels. Creating a scheduled task takes twelve clicks and three dialog boxes. A cron job is one line. One. Line.

“I set a reminder and do it manually.” You’re the scheduler. You’re the runtime. You’re the single point of failure. When you’re sick, on vacation, or asleep — nothing runs. cron doesn’t sleep.

“Systemd timers are more modern.” Systemd timers are more flexible — they support calendar expressions, randomized delays, and dependency management. They also require creating two files (a .timer and a .service) for what cron does in one line. For simple scheduled tasks, cron is less overhead. For complex service orchestration, sure, use systemd timers.

“I use a CI/CD pipeline for scheduled tasks.” Running a daily backup through GitHub Actions is using a fighter jet to deliver mail. cron runs locally, instantly, with zero infrastructure dependencies. Save CI/CD for builds and deployments.


cron cheat sheet

You made it. Or you skipped straight here. Either way, no judgment. Copy and paste these. Pin them. Tattoo them on your forearm. Whatever works.

What you’re doing Cron expression
Every minute * * * * * command
Every hour 0 * * * * command
Every day at midnight 0 0 * * * command
Every day at 3:30 AM 30 3 * * * command
Every Monday at 9 AM 0 9 * * 1 command
Weekdays at 6 PM 0 18 * * 1-5 command
Every 15 minutes */15 * * * * command
First of every month 0 12 1 * * command
Log output 0 2 * * * cmd >> /var/log/job.log 2>&1
Discard output 0 2 * * * cmd > /dev/null 2>&1

The one thing to remember: crontab -e to edit, and always use full paths. Everything else you can look up.

Back to the top, you overachiever.