BytePane

Cron Jobs Tutorial: Schedule Tasks Like a Pro

Linux15 min read

What Are Cron Jobs?

Cron is a time-based job scheduler built into Unix and Linux operating systems. It lets you run commands or scripts automatically at specified intervals -- every minute, every hour, every Monday at 3 AM, or on the first day of each month. The cron daemon (crond) runs in the background and checks every minute whether any scheduled jobs need to execute.

Cron jobs are fundamental to system administration and backend development. They handle database backups, log rotation, cache clearing, report generation, health checks, certificate renewal, and thousands of other recurring tasks. If you have ever scheduled a task to run automatically on a server, you have probably used cron.

Writing cron expressions by hand is notoriously error-prone because of the compact syntax. Our Crontab Generator lets you build cron schedules visually and see a human-readable description of when the job will run, eliminating guesswork entirely.

Cron Syntax: The Five Fields Explained

A cron expression consists of five fields separated by spaces, followed by the command to execute. Each field represents a unit of time, and together they define exactly when the job runs.

# ┌───────────── 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
  0 3 * * *  /usr/local/bin/backup.sh
FieldAllowed ValuesSpecial Characters
Minute0-59* , - /
Hour0-23* , - /
Day of Month1-31* , - /
Month1-12 or JAN-DEC* , - /
Day of Week0-7 or SUN-SAT* , - /

Special Characters

CharacterMeaningExample
*Every value in the field* * * * * = every minute
,List of values1,15 = 1st and 15th
-Range of values1-5 = Monday through Friday
/Step values*/15 = every 15 units

Special Cron Strings

Most cron implementations support shorthand strings that replace the five-field expression. These are easier to read and less error-prone for common schedules.

StringEquivalentDescription
@yearly0 0 1 1 *Once a year (midnight, Jan 1)
@monthly0 0 1 * *Once a month (midnight, 1st)
@weekly0 0 * * 0Once a week (midnight, Sunday)
@daily0 0 * * *Once a day (midnight)
@hourly0 * * * *Once an hour (at minute 0)
@rebootN/AOnce at system startup
# These two lines are identical:
0 0 * * *  /usr/local/bin/cleanup.sh
@daily     /usr/local/bin/cleanup.sh

# Run a script once when the system boots
@reboot    /home/deploy/start-app.sh

Common Cron Schedule Patterns

Here are the most frequently used cron expressions in real-world server administration and application development. You can verify any of these in our Crontab Generator to see a human-readable description of the schedule.

# Every 5 minutes
*/5 * * * *  /scripts/health-check.sh

# Every hour at minute 30
30 * * * *  /scripts/sync-data.sh

# Every day at 3:00 AM
0 3 * * *  /scripts/backup-database.sh

# Every Monday at 9:00 AM
0 9 * * 1  /scripts/weekly-report.sh

# Every weekday (Mon-Fri) at 8:00 AM
0 8 * * 1-5  /scripts/morning-tasks.sh

# First day of every month at midnight
0 0 1 * *  /scripts/monthly-cleanup.sh

# Every 15 minutes during business hours (9-17)
*/15 9-17 * * 1-5  /scripts/check-queue.sh

# Twice a day at 6 AM and 6 PM
0 6,18 * * *  /scripts/generate-report.sh

# Every Sunday at 2:30 AM
30 2 * * 0  /scripts/full-backup.sh

# Last day of February (run on 28th, skip non-leap years)
0 0 28 2 *  /scripts/leap-year-check.sh

A common mistake is confusing the day-of-month and day-of-week fields. When both are specified with non-wildcard values, cron runs the job if either condition is true (OR logic), not when both are true (AND logic). This surprises many developers and is a frequent source of scheduling bugs.

Managing Crontab: Essential Commands

The crontab command is the primary interface for creating, viewing, and editing cron jobs. Each user has their own crontab file, and root has access to a system-wide crontab as well.

# View your current crontab
crontab -l

# Edit your crontab (opens in default editor)
crontab -e

# Remove all your cron jobs (BE CAREFUL)
crontab -r

# View another user's crontab (root only)
crontab -u username -l

# Install a crontab from a file
crontab /path/to/crontab-file

# Set the editor for crontab -e
export VISUAL=nano
# or
export EDITOR=vim

System-Wide vs User Crontab

There are two types of cron configurations. User crontabs are managed with crontab -e and stored in /var/spool/cron/. System crontabs live in /etc/crontab and /etc/cron.d/ and include an extra field for the username.

# User crontab (no username field):
0 3 * * *  /home/deploy/backup.sh

# System crontab (/etc/crontab) - has username field:
0 3 * * *  root  /usr/local/bin/backup.sh

# System cron directories (scripts placed here run automatically):
/etc/cron.hourly/   # Runs every hour
/etc/cron.daily/    # Runs once per day
/etc/cron.weekly/   # Runs once per week
/etc/cron.monthly/  # Runs once per month

Understanding file permissions is essential when working with cron jobs, since the scripts cron executes must have the correct execute permissions. Our Linux File Permissions guide and Chmod Calculator can help you set these correctly.

Environment Variables and PATH

One of the most common reasons cron jobs fail silently is the difference between your interactive shell environment and the cron environment. When cron runs your command, it uses a minimal environment -- your PATH, shell aliases, and environment variables are not available unless you explicitly set them.

# BAD: This might fail in cron because 'node' is not in cron's PATH
*/5 * * * *  node /home/deploy/app.js

# GOOD: Use the full path to the executable
*/5 * * * *  /usr/local/bin/node /home/deploy/app.js

# GOOD: Set PATH at the top of your crontab
PATH=/usr/local/bin:/usr/bin:/bin
*/5 * * * *  node /home/deploy/app.js

# Set environment variables in crontab
SHELL=/bin/bash
[email protected]
NODE_ENV=production

0 3 * * *  /home/deploy/backup.sh

Best practice: always use absolute paths for both the interpreter and the script. Run which node or which python3 to find the full path to your interpreters, and use those paths in your crontab.

Logging and Output Redirection

By default, cron sends any output (stdout and stderr) from your job as an email to the user who owns the crontab. On most servers, this email goes nowhere because no local mail transport is configured. This means errors are silently swallowed. Always redirect output explicitly.

# Redirect stdout to a log file, stderr to the same file
0 3 * * *  /scripts/backup.sh >> /var/log/backup.log 2>&1

# Redirect stdout to log, stderr to a separate error log
0 3 * * *  /scripts/backup.sh >> /var/log/backup.log 2>> /var/log/backup-error.log

# Discard all output (not recommended -- you lose error info)
0 3 * * *  /scripts/backup.sh > /dev/null 2>&1

# Add timestamps to log entries
0 3 * * *  echo "$(date '+\%Y-\%m-\%d \%H:\%M:\%S') Starting backup" >> /var/log/backup.log && /scripts/backup.sh >> /var/log/backup.log 2>&1

# Send email on failure only
0 3 * * *  /scripts/backup.sh > /dev/null 2>&1 || echo "Backup failed" | mail -s "Backup Alert" [email protected]

Important: percent signs (%) have special meaning in crontab files -- they are converted to newlines. If your command uses percent signs (common with date formatting), you must escape them with a backslash: \%.

Debugging Cron Jobs

Cron jobs fail silently more often than any other system component. When a job does not run or produces unexpected results, follow this systematic debugging checklist.

  1. Check that cron is running -- Run systemctl status cron (or crond on RHEL/CentOS). If it is stopped, start it.
  2. Check the cron log -- Cron logs every job execution to syslog. Check /var/log/syslog or /var/log/cron for entries mentioning your job.
  3. Test the command manually -- Run the exact command from the crontab in a fresh shell with env -i /bin/bash --noprofile --norc to simulate the cron environment.
  4. Verify PATH and permissions -- Use absolute paths for all executables. Ensure the script has execute permission (chmod +x script.sh).
  5. Check for percent signs -- Unescaped % characters silently break cron commands. Escape them with \%.
  6. Verify the cron expression -- Use our Crontab Generator to double-check that the schedule is what you intend.
  7. Check for overlapping runs -- If a job takes longer than its interval, you may get multiple simultaneous instances. Use flock for locking (shown below).
# Check if cron daemon is running
systemctl status cron

# View cron log entries for your jobs
grep CRON /var/log/syslog | tail -20

# Simulate the cron environment for testing
env -i SHELL=/bin/bash HOME=$HOME PATH=/usr/bin:/bin /your/script.sh

# Prevent overlapping job runs with flock
*/5 * * * *  flock -n /tmp/myjob.lock /scripts/long-running-job.sh

Real-World Cron Job Examples

Here are production-ready cron job patterns that cover the most common server administration and application maintenance tasks. Each example includes proper logging and error handling.

Database Backup with Rotation

#!/bin/bash
# /scripts/backup-postgres.sh
# Run daily at 2 AM: 0 2 * * * /scripts/backup-postgres.sh

BACKUP_DIR="/backups/postgres"
DATE=$(date +%Y%m%d_%H%M%S)
DB_NAME="production"

# Create backup
pg_dump $DB_NAME | gzip > "$BACKUP_DIR/$DB_NAME-$DATE.sql.gz"

# Delete backups older than 30 days
find $BACKUP_DIR -name "*.sql.gz" -mtime +30 -delete

echo "$(date) Backup completed: $DB_NAME-$DATE.sql.gz"

SSL Certificate Auto-Renewal

# Attempt renewal twice daily (Let's Encrypt recommends this)
0 0,12 * * * /usr/bin/certbot renew --quiet --post-hook "systemctl reload nginx" >> /var/log/certbot.log 2>&1

Log Cleanup and Rotation

# Clean application logs older than 7 days every Sunday at 1 AM
0 1 * * 0 find /var/log/myapp/ -name "*.log" -mtime +7 -delete >> /var/log/cleanup.log 2>&1

# Compress logs older than 1 day, delete after 30 days
0 2 * * * find /var/log/myapp/ -name "*.log" -mtime +1 ! -name "*.gz" -exec gzip {} \;
0 3 * * * find /var/log/myapp/ -name "*.log.gz" -mtime +30 -delete

Health Check with Alerting

#!/bin/bash
# /scripts/health-check.sh
# Run every 5 minutes: */5 * * * * /scripts/health-check.sh

URL="https://myapp.com/health"
STATUS=$(curl -s -o /dev/null -w "%{http_code}" --max-time 10 "$URL")

if [ "$STATUS" != "200" ]; then
  echo "$(date) ALERT: Health check failed with status $STATUS" >> /var/log/health.log
  curl -X POST "https://hooks.slack.com/services/..." \
    -d '{"text":"Health check failed! Status: '"$STATUS"'"}'
fi

The health check script uses HTTP status codes to determine whether the application is healthy. For a deep understanding of what each status code means, see our HTTP Status Codes reference tool.

Cron Alternatives: systemd Timers

On modern Linux distributions, systemd timers offer an alternative to cron with more features: dependency management, resource control (CPU/memory limits), calendar-based scheduling with timezone support, and built-in logging through journald. While cron remains the simpler and more portable choice, systemd timers are worth considering for complex scheduling requirements.

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

[Timer]
OnCalendar=*-*-* 03:00:00
Persistent=true
RandomizedDelaySec=300

[Install]
WantedBy=timers.target

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

[Service]
Type=oneshot
ExecStart=/scripts/backup-postgres.sh
User=postgres

# Enable and start the timer
# systemctl enable --now backup.timer
# systemctl list-timers --all

The Persistent=true option ensures missed runs are executed on next boot, and RandomizedDelaySec adds jitter to prevent all servers in a cluster from running backups at the exact same second.

Cron Security Best Practices

Cron jobs run with the permissions of the user who owns the crontab. A misconfigured cron job can be a serious security vulnerability. Follow these best practices to keep your scheduled tasks secure.

  1. Use the principle of least privilege -- Never run cron jobs as root unless absolutely necessary. Create dedicated service accounts with minimal permissions.
  2. Restrict crontab access -- Use /etc/cron.allow and /etc/cron.deny to control which users can create cron jobs.
  3. Secure script permissions -- Scripts executed by cron should be owned by the user running them and not writable by others. Use chmod 700 for sensitive scripts.
  4. Never hardcode credentials -- Use environment variables, secrets managers, or restricted-access configuration files instead of putting passwords directly in crontab entries or scripts.
  5. Audit cron jobs regularly -- Review all user and system crontabs periodically. An attacker who gains shell access may install a persistent backdoor via cron.
  6. Log everything -- Always redirect output to log files. Silent failures in cron jobs can go unnoticed for weeks or months.

Frequently Asked Questions

Why is my cron job not running?

The most common causes are: the cron daemon is stopped, the cron expression has a syntax error, the script lacks execute permissions, the command relies on environment variables not available in cron, or the PATH does not include the required executables. Check /var/log/syslog for cron entries and always use absolute paths.

How do I run a cron job every 30 seconds?

Cron's minimum resolution is one minute. To run something every 30 seconds, create two cron entries: one normal and one with a 30-second sleep: * * * * * /script.sh and * * * * * sleep 30 && /script.sh. For sub-minute scheduling, consider using a systemd timer with OnUnitActiveSec=30s or a process manager like supervisord.

What timezone does cron use?

By default, cron uses the system timezone. On most servers this is UTC. You can check with timedatectl. Some cron implementations support a CRON_TZ variable at the top of the crontab to override the timezone for all jobs in that file. systemd timers offer more flexible timezone handling.

How do I prevent overlapping cron job runs?

Use flock to create a lock file: flock -n /tmp/myjob.lock /scripts/job.sh. The -n flag makes flock exit immediately (without running the job) if another instance already holds the lock. This ensures only one instance runs at a time.

Build Cron Schedules Visually

Stop memorizing cron syntax. Our free Crontab Generator lets you build schedules with a visual interface and instantly see a human-readable description of when the job will run. No more guesswork.

Open Crontab Generator

Related Articles