Cycle Logo
  • Deploy anything, anywhere
  • Build your own private cloud
  • Eliminates DevOps sprawl

Using Cron and Scheduling Jobs

If you have ever needed to run the same command repeatedly, whether it is backing up a database, rotating logs, or sending a daily report, you have probably wished for a reliable way to automate it. On Unix and Linux systems, the most common answer is Cron, a time-based job scheduler that has been around since the 1970s and is still one of the most widely used automation tools today.

Cron runs quietly in the background as a daemon, checking a special configuration file called the crontab for instructions. Each instruction tells Cron when and how to run a command. By combining multiple entries, you can automate everything from simple hourly checks to complex multi-step workflows.

Here is a taste of what Cron looks like in practice. This job runs a backup script every night at 2 a.m. and saves both output and errors to a log file:

0 2 * * * /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1

The first five fields (0 2 * * *) define when the job runs. Cron interprets these fields as minute, hour, day, month, and weekday. In this case, it means “at 2:00 a.m. every day.” The rest of the line is the command you want executed.

By the end of this guide you will be able to:

  • Understand Cron’s scheduling format and how the daemon works.
  • Write and manage your own Cron jobs, from the simplest to the most advanced.
  • Troubleshoot common issues when jobs do not run as expected.
  • Explore alternatives like systemd timers or anacron when Cron is not the right fit.

In short, you will learn how to turn manual, repetitive tasks into reliable background processes that run exactly when you need them to, with no babysitting required.

Understanding Cron

Cron is both a daemon (a background process) and a job scheduler. When it is running, it regularly checks a set of configuration files called crontabs to see if any jobs are due. Each entry in a crontab describes a schedule and the command that should be executed.

The basic format of a Cron job looks like this:

* * * * * command_to_run

The five asterisks represent time fields. Each one tells Cron when the command should run. Here is a breakdown:

FieldValuesExampleMeaning
Minute0–590On the 0th minute (top of the hour)
Hour0–232At 2 AM
Day1–3115On the 15th day of the month
Month1–126In June
Weekday0–6 (Sun–Sat)0On Sunday

Cron combines these fields to decide exactly when to run your job. For example:

0 2 * * * /usr/local/bin/backup.sh

This means “run the backup script every day at 2:00 a.m.”

You can also use wildcards and ranges. For instance:

*/15 * * * * /usr/local/bin/healthcheck.sh

This runs the command every 15 minutes, no matter what the hour or day.

Another example shows how to run something only on weekdays at 9 a.m.:

0 9 * * 1-5 /usr/bin/send-report.sh

Here 1-5 represents Monday through Friday.

Common Pitfalls

The most frequent mistake with Cron is misreading the fields. A new user might think the first field is the hour and end up scheduling jobs every minute instead of once per hour. For example:

2 * * * * /usr/bin/mistake.sh

This actually runs at 2 minutes past every hour, not at 2 a.m.

Another pitfall is forgetting that Cron does not inherit your shell’s environment. Commands that work fine in an interactive terminal might fail under Cron unless you use full paths. For example, this might fail:

0 * * * * python3 script.py

but this will succeed:

0 * * * * /usr/bin/python3 /home/user/script.py

Practical Applications

Even with just these basics, Cron can already automate common tasks such as:

  • Nightly backups
  • Clearing temporary files every morning
  • Sending out daily reports

For instance, here is a simple job that clears the /tmp folder at 6 a.m. every day:

0 6 * * * rm -rf /tmp/*

This may look small, but it illustrates the real power of Cron: once configured, it silently handles repetitive work so you do not have to.

Writing Cron Jobs

Now that you know what Cron is and how its timing fields work, the next step is writing your own jobs. Cron jobs live inside a crontab, which you can edit with:

crontab -e

Each line in the crontab represents one job. Cron reads the timing fields first, then the command to run.

Using Crontab Syntax

The most common schedules can be expressed in just one line. Here are a few examples, with the schedule on the left and the command on the right:

0 0 * * * /usr/bin/backup.sh                 # Every day at midnight
*/5 * * * * /usr/bin/healthcheck.sh          # Every 5 minutes
0 9 * * 1-5 /usr/bin/send-report.sh          # Weekdays at 9 AM
30 2 1 * * /usr/bin/monthly-maintenance.sh   # On the 1st of each month at 2:30 AM

Notice how comments can be added with a # to make schedules easier to understand.

Special Strings

Cron also supports shorthand strings that replace the five time fields. This is useful when you do not want to remember exact field syntax.

StringMeaningExample
@rebootRun once at startup@reboot /usr/bin/startup-task.sh
@hourlyRun every hour@hourly /usr/bin/hourly-task.sh
@dailyRun once a day at midnight@daily /usr/bin/daily-report.sh
@weeklyRun once a week (Sunday midnight)@weekly /usr/bin/weekly-report.sh
@monthlyRun once a month (1st at midnight)@monthly /usr/bin/monthly-cleanup.sh

For example, instead of writing 0 0 * * *, you can simply write:

@daily /usr/bin/backup.sh

Redirecting Output

By default, Cron will send job output to the local mail spool of the user who owns the crontab. This can clutter up mailboxes quickly, so most jobs explicitly redirect output to a log file.

0 3 * * * /usr/bin/cleanup.sh >> /var/log/cleanup.log 2>&1
  • >> appends standard output to the log file.
  • 2>&1 redirects standard error to the same file.

If you want to discard all output, redirect it to /dev/null:

0 3 * * * /usr/bin/cleanup.sh > /dev/null 2>&1

Time Zones and Daylight Saving

Cron uses the system’s local time zone by default. If your server runs in UTC but you want a job to run at 9 AM New York time, you can set the TZ variable at the top of your crontab:

TZ=America/New_York
0 9 * * * /usr/bin/report.sh

Be careful with daylight saving transitions. For example, a 2 AM job in New York will be skipped or run twice on the days clocks shift. If this matters for your workload, it may be safer to keep jobs in UTC and adjust reports downstream.

Practical Example

Here is a small crontab that combines several types of jobs:

# Back up database every night
0 2 * * * /usr/local/bin/db-backup.sh >> /var/log/db-backup.log 2>&1
 
# Clear temp files every morning
0 6 * * * rm -rf /tmp/*
 
# Send daily report using system time zone
TZ=UTC
0 10 * * * /usr/local/bin/report.sh >> /var/log/report.log 2>&1
 
# Run custom script on reboot
@reboot /usr/local/bin/init-setup.sh

Even with just these examples, you can already build a solid foundation of automated tasks.

Advanced Cron Features

Once you are comfortable with simple schedules, Cron offers additional features that make it far more flexible. These include environment variables, system-wide crontabs, job dependencies, and security considerations.

Environment Variables

A crontab does not automatically inherit your interactive shell’s environment. This means variables like PATH or MAILTO need to be defined explicitly. At the top of your crontab, you can set variables that apply to all jobs.

# Send all job output to this email
MAILTO=admin@example.com
 
# Set a safe PATH to avoid surprises
PATH=/usr/local/bin:/usr/bin:/bin
 
# Job will use the above environment
0 5 * * * backup.sh

Without a proper PATH, jobs may fail because Cron cannot find executables that work fine in your shell.

System Crontabs vs User Crontabs

In addition to user crontabs (managed with crontab -e), there is also a system-wide crontab in /etc/crontab. The difference is that system crontabs have an extra user field that specifies who the job should run as.

Example /etc/crontab:

# m h dom mon dow user command
0 1 * * * root /usr/bin/system-maintenance
30 6 * * 1 postgres /usr/bin/pg_dumpall > /var/backups/all.sql

This allows root to schedule jobs for other users, which is common for database maintenance or system cleanup tasks.

Handling Dependencies

Cron itself has no concept of job dependencies. If you need job B to run only after job A succeeds, you can wrap both commands in a script:

#!/bin/bash
/usr/local/bin/job-a.sh && /usr/local/bin/job-b.sh

Then schedule the wrapper script:

0 4 * * * /usr/local/bin/wrapper.sh >> /var/log/dependent-jobs.log 2>&1

The && ensures that job-b.sh only runs if job-a.sh exits successfully.

Another approach is to use lockfiles to prevent overlapping jobs. For example:

* * * * * flock -n /tmp/myjob.lock /usr/local/bin/myjob.sh

This ensures the job will not run twice at the same time.

Security Considerations

Because Cron runs jobs automatically, it is important to treat crontabs carefully:

  • Use absolute paths: Do not rely on relative paths that may vary between environments.
  • Restrict permissions: Only trusted users should be allowed to edit crontabs. On most systems, /etc/cron.allow and /etc/cron.deny control access.
  • Avoid insecure scripts: If your job runs shell scripts, make sure they are not world-writable and that they sanitize any input.

Example of locking down a script:

chmod 700 /usr/local/bin/backup.sh
chown root:root /usr/local/bin/backup.sh

This ensures only root can modify or execute it.

Practical Example

Here is a more advanced crontab that combines multiple concepts:

# Environment variables
MAILTO=ops@example.com
PATH=/usr/local/bin:/usr/bin:/bin
 
# Backup job runs as root, with lock to avoid overlap
0 1 * * * root flock -n /tmp/db.lock /usr/local/bin/db-backup.sh >> /var/log/db-backup.log 2>&1
 
# Wrapper script ensures cleanup runs only if archive succeeded
30 2 * * * root /usr/local/bin/archive-and-clean.sh >> /var/log/archive.log 2>&1

This setup sends output by email, ensures jobs do not overlap, and respects dependencies between backup and cleanup.

Monitoring and Troubleshooting Cron Jobs

Even the best-written Cron jobs sometimes fail silently. Monitoring and troubleshooting are critical to make sure your scheduled tasks actually do what you expect.

Checking Logs

Cron writes logs to system log files. On Debian and Ubuntu systems you can check the syslog file:

grep CRON /var/log/syslog

On Red Hat or CentOS systems, use:

grep CRON /var/log/cron

If your system uses systemd, you can view logs with:

journalctl -u cron

These logs show when jobs were triggered and whether they exited successfully.

Capturing Job Output

By default, Cron sends job output to the user’s local mail. A better approach is to redirect output to log files so you can check them later.

0 * * * * /usr/local/bin/sync.sh >> /var/log/sync.log 2>&1

Now, both standard output and error messages go into /var/log/sync.log. If something goes wrong, you will see exactly what failed.

For temporary debugging, you might want a lightweight log in /tmp:

*/10 * * * * /usr/bin/myjob.sh >> /tmp/myjob.log 2>&1

This way you can test quickly without polluting production logs.

Common Errors and Fixes

Here are some common issues and how to resolve them:

ProblemSymptomFix
Wrong pathScript runs fine in shell but fails in CronUse full paths like /usr/bin/python3
Missing environment variablesJob fails with “command not found”Set PATH or required variables in crontab
Permission deniedCron cannot execute scriptEnsure script is executable: chmod +x script.sh
No outputJob seems to do nothingRedirect output to a log file for debugging
Job overlapsMultiple runs cause conflictsUse flock or wrapper scripts to prevent overlaps

Testing Jobs Before Scheduling

It is always better to test jobs manually before adding them to the crontab. Run the exact command in your shell and confirm it works. Once confirmed, add it to the crontab with full paths and logging.

For instance, if your job is:

0 2 * * * /usr/local/bin/db-backup.sh >> /var/log/db-backup.log 2>&1

First test:

/usr/local/bin/db-backup.sh

Then confirm the output and permissions before scheduling.

Using Tools for Monitoring

For critical systems, you may want monitoring beyond basic logs. A few approaches include:

  • Systemd timers: They integrate with systemctl status and journalctl for better logging.
  • Third-party monitoring: Tools like monit, supervisord, or even a custom script that checks for log freshness.

A simple DIY approach is to touch a “heartbeat file” whenever a job succeeds, then monitor its timestamp:

0 * * * * /usr/local/bin/task.sh && touch /tmp/task.success

You can then use find to alert if the file is too old:

find /tmp/task.success -mmin +65 && echo "Task missed" | mail -s "Cron Alert" admin@example.com

Monitoring Cron jobs in Docker, logs to docker logs

You can run Cron inside a minimal container and send every job’s output to the container’s stdout and stderr. That gives you instant visibility with docker logs, plus you can attach your existing log pipeline without extra work.

Dockerfile

FROM alpine:3.20
RUN apk add --no-cache bash busybox-suid tzdata
ENV TZ=UTC
WORKDIR /app
COPY scripts/ /usr/local/bin/
COPY crontab /etc/crontabs/root
RUN chmod 700 /usr/local/bin/*.sh
CMD ["crond", "-f", "-l", "8"]

crontab

*/5 * * * * /usr/local/bin/task.sh >> /proc/1/fd/1 2>> /proc/1/fd/2

scripts/task.sh

#!/usr/bin/env bash
set -euo pipefail
echo "[task] starting at $(date -Is)"
curl -fsS https://example.com/health > /dev/null && echo "[task] health check passed"
echo "[task] done at $(date -Is)"

Run the container and check logs:

docker build -t myorg/cron-jobs .
docker run --rm --name cron myorg/cron-jobs
docker logs -f cron

This approach lets you keep jobs containerized, monitor them easily with Docker tooling, and control time zones and locks explicitly.

Alternatives to Cron

Cron has been around for decades and works well for most recurring tasks. However, it is not always the best choice. Some jobs need stronger dependency management, better handling of missed runs, or integration with containerized environments. In those cases, alternative schedulers may be a better fit.

Anacron

Unlike Cron, which skips jobs if the system is down at the scheduled time, Anacron ensures that missed jobs are run once the system comes back online.

# /etc/anacrontab
1   5   daily-backup   /usr/local/bin/backup.sh

at

The at command schedules a one-time job.

echo "/usr/bin/systemctl restart nginx" | at 2am

Check pending jobs:

atq

Remove one:

atrm <job_id>

systemd Timers

On systems that use systemd, timers can be more powerful than Cron.

backup.service

[Unit]
Description=Database backup
 
[Service]
Type=oneshot
ExecStart=/usr/local/bin/db-backup.sh

backup.timer

[Unit]
Description=Run database backup daily
 
[Timer]
OnCalendar=*-*-* 02:00:00
Persistent=true
 
[Install]
WantedBy=timers.target

Enable and start:

systemctl enable --now backup.timer

Comparing Options

FeatureCronAnacronatsystemd timer
Repeated schedules
Handles missed runsN/A
One-time jobs
Dependency handlingLimitedLimitedN/AStrong
Logging integrationBasicBasicBasicJournald
Good for laptops

Wrapping Up

Cron remains one of the simplest and most effective tools for automating repetitive tasks on Unix and Linux systems. With just a few lines in a crontab, you can schedule backups, clean up logs, or send daily reports without lifting a finger. By learning how to write jobs correctly, set environment variables, handle errors, and monitor execution, you can make Cron a reliable part of your infrastructure.

We also looked at alternatives such as Anacron, at, and systemd timers, each of which fills a niche where Cron falls short. If your workloads depend on strict dependencies, resilience to downtime, or richer logging, these options are worth exploring.

The best way to get comfortable with scheduling is to start small. Create a simple Cron job today, even something harmless like appending the current date to a file, and watch it run. From there you can build up to production-grade jobs with logging, locking, and monitoring.

*/5 * * * * echo "$(date)" >> /tmp/heartbeat.log

That one line shows the entire Cron model in action: schedule, command, output. Once you see it work, you will be ready to automate tasks that actually matter.

Now it is your turn: try scheduling your own jobs, experiment with advanced features, and share what works best in your environment.

🍪 Help Us Improve Our Site

We use first-party cookies to keep the site fast and secure, see which pages need improved, and remember little things to make your experience better. For more information, read our Privacy Policy.