beginner 15 min read

How to Set Up Cron Jobs on Linux (crontab Guide)

Complete guide to setting up cron jobs on Linux using crontab. Covers cron syntax, environment variables, logging, system vs user crontab, and debugging.

Prerequisites

  • Linux server (Ubuntu, Debian, CentOS, etc.)
  • Terminal access
  • A text editor (nano, vim)

What Is Cron?

Cron is the standard job scheduler on Unix-like systems. The cron daemon (crond) runs in the background and checks every minute for tasks to execute. Tasks are defined in crontab (cron table) files.

Every Linux distribution includes cron by default. It's the foundation that most other scheduling tools build on.

Quick Start: crontab -e

Edit your crontab:

bash
crontab -e

This opens your user's crontab in the default editor. Add a line:

bash
*/5 * * * * echo "Hello from cron" >> /tmp/cron.log 2>&1

Save and exit. Your job is now scheduled.

List your crontab:

bash
crontab -l

Remove your crontab (all jobs):

bash
crontab -r

Cron Expression Syntax

A cron expression has 5 fields:

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

Special characters:

  • * — any value
  • , — list: 1,3,5
  • - — range: 1-5
  • / — step: */5 (every 5)

Examples:

  • */5 * * * * — Every 5 minutes
  • 0 9 * * 1-5 — Weekdays at 9 AM
  • 0 0 1,15 * * — 1st and 15th of each month at midnight

Special Strings

Most cron implementations support shorthand strings:

  • @reboot — Run once at startup
  • @hourly — Same as 0 * * * *
  • @daily (or @midnight) — Same as 0 0 * * *
  • @weekly — Same as 0 0 * * 0
  • @monthly — Same as 0 0 1 * *
  • @yearly (or @annually) — Same as 0 0 1 1 *
bash
@reboot /opt/myapp/start.sh
@daily /opt/myapp/backup.sh

Common Cron Expressions

  • * * * * * — Every minute
  • */5 * * * * — Every 5 minutes
  • */15 * * * * — Every 15 minutes
  • 0 * * * * — Every hour
  • 0 0 * * * — Daily at midnight
  • 0 9 * * * — Daily at 9 AM
  • 0 9 * * 1-5 — Weekdays at 9 AM
  • 0 0 * * 0 — Every Sunday at midnight
  • 0 0 1 * * — First day of every month
  • 0 0 1 1 * — January 1st at midnight

Every weekday at 9:00 AM

Next runs (UTC):

Mon, May 18, 2026 09:00

Tue, May 19, 2026 09:00

Wed, May 20, 2026 09:00

System Crontab vs User Crontab

User crontab (crontab -e):

  • Per-user schedule
  • Stored in /var/spool/cron/crontabs/
  • Commands run as that user
  • No user field in the entry

System crontab (/etc/crontab):

  • System-wide schedule
  • Includes a user field between the time and command
  • Requires root to edit
bash
# /etc/crontab format (note the user field)
*/5 * * * * root /opt/scripts/cleanup.sh
0 2 * * *  backup /opt/scripts/backup.sh

Cron directories:

  • /etc/cron.d/ — Drop-in cron files (system crontab format)
  • /etc/cron.hourly/ — Scripts run every hour
  • /etc/cron.daily/ — Scripts run daily
  • /etc/cron.weekly/ — Scripts run weekly
  • /etc/cron.monthly/ — Scripts run monthly

Environment Variables

Cron runs with a minimal environment — your .bashrc, .profile, and PATH are NOT loaded. This is the #1 cause of "works in terminal but not in cron" issues.

Set variables in your crontab:

bash
PATH=/usr/local/bin:/usr/bin:/bin
MAILTO=admin@example.com
SHELL=/bin/bash

*/5 * * * * /opt/myapp/task.sh

Or source your profile in the command:

bash
*/5 * * * * . $HOME/.profile; /opt/myapp/task.sh

MAILTO: Set this to receive email notifications of cron output. Set MAILTO="" to suppress emails.

Output & Logging

By default, cron emails any output to the user. For better logging:

Redirect to a log file:

bash
*/5 * * * * /opt/myapp/task.sh >> /var/log/myapp-cron.log 2>&1
  • >> appends stdout to the file
  • 2>&1 redirects stderr to stdout

Discard output:

bash
*/5 * * * * /opt/myapp/task.sh > /dev/null 2>&1

Log with timestamps:

bash
*/5 * * * * /opt/myapp/task.sh 2>&1 | while read line; do echo "$(date '+\%Y-\%m-\%d \%H:\%M:\%S') $line"; done >> /var/log/cron.log

Check cron's own log:

bash
grep CRON /var/log/syslog       # Ubuntu/Debian
journalctl -u cron              # systemd
grep cron /var/log/cron         # CentOS/RHEL

Debugging: "Why Is My Cron Job Not Running?"

Checklist for debugging:

1. Is cron running?

bash
systemctl status cron    # or crond on CentOS

2. Is the crontab installed?

bash
crontab -l

3. Is the PATH correct?

Use full paths to all binaries: /usr/bin/python3 not just python3

4. Are permissions correct?

Scripts must be executable: chmod +x /opt/myapp/task.sh

5. Check cron logs:

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

6. Check for errors by redirecting output:

bash
*/5 * * * * /opt/myapp/task.sh >> /tmp/cron-debug.log 2>&1

7. Is the user allowed to use cron?

Check /etc/cron.allow and /etc/cron.deny.

Production Tips

Lock files to prevent overlapping:

bash
*/5 * * * * flock -n /tmp/myapp.lock /opt/myapp/task.sh

flock -n exits immediately if the lock is held, preventing overlapping runs.

Timeouts:

bash
0 2 * * * timeout 3600 /opt/myapp/backup.sh

Kills the job after 1 hour if it's still running.

Monitoring: Ping a health check service on success:

bash
0 * * * * /opt/myapp/task.sh && curl -s https://hc-ping.com/uuid > /dev/null

Centralized logging: Use logger to send output to syslog:

bash
*/5 * * * * /opt/myapp/task.sh 2>&1 | logger -t myapp-cron

Frequently Asked Questions