Cron Jobs Explained: Schedule Tasks on Linux Without the Surprises
Learn cron syntax, where jobs are stored, why they run with a minimal environment, and how to prevent overlaps, capture output and debug a job that never ran.
Cron is the Linux scheduler that runs commands at fixed times: nightly backups, certificate renewals, cache clean-ups, report emails. It is simple and reliable, yet it is also the source of a classic frustration: a command works in your terminal and fails silently under cron. Most of those failures come from the same few causes, all covered below.
The five-field syntax
Each line in a crontab has five time fields followed by the command:
# ┌ minute (0-59)
# │ ┌ hour (0-23)
# │ │ ┌ day of month (1-31)
# │ │ │ ┌ month (1-12)
# │ │ │ │ ┌ day of week (0-7, Sunday is 0 or 7)
# │ │ │ │ │
# * * * * * command
An asterisk means "every". Some useful examples:
| Schedule | Meaning |
|---|---|
*/5 * * * * | Every five minutes |
0 3 * * * | Every day at 03:00 |
30 2 * * 1 | Mondays at 02:30 |
0 0 1 * * | The first day of each month at midnight |
@reboot | Once when the system starts |
One quirk: if you restrict both day of month and day of week, cron runs the job when either matches, not both. Also, a literal percent sign has a special meaning in crontab lines and must be escaped as \%, which matters for commands such as date +\%F.
Where crontabs live
- User crontabs: edit your own with
crontab -eand list withcrontab -l. Jobs run as that user. - System crontab:
/etc/crontaband files in/etc/cron.d/have an extra column naming the user to run as. - Convenience folders: scripts placed in
/etc/cron.daily,cron.hourlyand similar run on those intervals without any schedule syntax.
Use the account that should own the task. A backup that only reads files does not need root, and a web app task should run as the web app user, so files it creates have the right owner.
Why jobs fail: the minimal environment
Here is what a realistic crontab looks like, with variables first and then the jobs:
PATH=/usr/local/bin:/usr/bin:/bin
MAILTO=""
15 3 * * * /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1
Setting MAILTO="" turns off emails entirely, which is sensible when you log to a file. The reason for the explicit PATH line is that cron does not load your shell profile. Its PATH is short, often just /usr/bin:/bin, and variables you set in .bashrc do not exist. The practical rules are:
- Use absolute paths for every command and file:
/usr/bin/phprather thanphp. Find them withwhich. - Set variables at the top of the crontab if needed, for example
PATH=/usr/local/bin:/usr/bin:/binand[email protected]. - Do not rely on the current directory. Use
cd /path/to/project && commandor full paths.
Capture output, prevent overlaps, mind the clock
By default, cron emails any output to the user, which only works if the server has mail configured. A better habit is to write to a log:
*/10 * * * * /usr/bin/flock -n /tmp/sync.lock /usr/local/bin/sync.sh >> /var/log/sync.log 2>&1
The redirection >> file 2>&1 sends both normal output and errors to the log. Check that the user running the job can write to that file. The flock -n wrapper prevents overlaps: if the previous run is still going, the new one exits immediately instead of piling up. Long-running jobs without such a guard can multiply and exhaust the server.
Time zones
Cron uses the server's time zone, normally the one shown by timedatectl. Daylight saving changes can cause a job scheduled in the skipped hour to be missed, or one in the repeated hour to run twice, depending on the implementation. Many administrators set servers to UTC to avoid this. Schedule heavy tasks away from your peak traffic hours, and not all at minute zero, so that jobs do not stampede together.
Debugging and alternatives
Debugging a job that does not run
- Confirm cron is running:
systemctl status cron(the service iscrondon Red Hat style systems). - Look at the log:
grep CRON /var/log/syslogon Debian and Ubuntu, orjournalctl -u cron; on Red Hat style systems,/var/log/cron. - Run the exact command as the exact user, with a clean environment:
sudo -u appuser env -i /bin/sh -c '/path/to/command'. - Check that the script has the executable bit and a proper first line such as
#!/bin/bash. - Make sure the crontab file ends with a newline; a missing final newline can make the last line be ignored on some systems.
When systemd timers are better
systemd timers do the same job and add useful features: logs in the journal by default, dependencies on other services, and a Persistent=true option that runs a missed job after the machine was off. Cron is simpler for quick tasks and universally understood; consider timers when you want those extras.
Good habits for scheduled jobs
Make each job idempotent where you can: running it twice should not cause damage. Keep the scheduled command short and put logic into a script that you can run by hand and test. Add a comment above each crontab line saying what it does and who owns it. Finally, monitor the outcome, not only the schedule: a nightly backup job that has been failing for three weeks looks exactly like a working one in the crontab. Our knowledge base has more on checking server health.
Scheduled tasks with Zebra Hosting
On Plesk-based hosting you can manage scheduled tasks from the control panel instead of editing crontabs by hand, and a VPS gives you full cron access.