How to Schedule Tasks With Cron (and Know When They Fail)

Cron syntax with examples, where jobs live, running them as the right user, capturing output, and how to get told when a job fails instead of finding out later.

Published
Reading time
3 min

Cron runs commands on a schedule: backups at 02:00, a Laravel scheduler every minute, a certificate renewal check daily. It has worked the same way for forty years, which is why the syntax looks strange and why it fails silently unless you tell it not to.

The syntax

A cron line is five time fields and a command:

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

* means every. Examples:

0 2 * * *        /usr/local/bin/backup.sh          # 02:00 every day
*/15 * * * *     /usr/bin/php /var/www/app/cron.php # every 15 minutes
0 */6 * * *      /usr/local/bin/sync.sh            # every 6 hours
30 3 * * 0       /usr/local/bin/weekly.sh          # 03:30 on Sundays
0 0 1 * *        /usr/local/bin/monthly.sh         # midnight on the 1st
* * * * *        cd /var/www/app && php artisan schedule:run  # Laravel

@daily, @hourly, @weekly and @reboot are accepted shorthands. crontab.guru explains any line in English; use it before saving anything you are unsure of.

Where jobs live

Per user: crontab -e opens your own crontab; jobs run as you. crontab -l lists it. Run sudo crontab -e for root's.

System-wide: /etc/cron.d/ holds files with an extra user field between the time and the command:

0 2 * * * www-data /usr/local/bin/backup.sh

This is the better place for jobs that belong to a server rather than a person — they survive user changes and are visible in one directory.

Plesk: each subscription has Scheduled Tasks in the panel, which writes the crontab for that subscription's user and offers email notification. See cron jobs in Plesk.

Three things that catch everyone

Cron's PATH is tiny. A command that works in your shell may fail in cron because php or node is not found. Use full paths (/usr/bin/php), or set PATH= at the top of the crontab.

No % unescaped. Cron treats % as a newline. date +%F in a cron line must be date +\%F.

It runs from your home directory. Scripts that expect to be in the project directory need cd /path && ....

Capture the output

By default cron mails any output to the local user, which on most servers means nowhere. Send it somewhere you will see:

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

>> appends stdout to a file; 2>&1 sends errors to the same place. Now tail /var/log/backup.log shows what happened last night. Rotate that log like any other.

Know when a job fails

This is the part most setups skip. A backup job that has been failing for three months is worse than no backup job, because you believed you had backups.

Email on failure only — the script exits non-zero when something goes wrong, and cron mails you only then:

MAILTO=you@example.com
0 2 * * * /usr/local/bin/backup.sh > /dev/null || echo "backup failed on $(hostname)"

Requires a working local mail setup (apt install msmtp or a configured Postfix).

A dead man's switch — the job pings a URL when it succeeds; the service alerts you if the ping stops coming. Healthchecks.io and Cronitor both have free tiers:

0 2 * * * /usr/local/bin/backup.sh && curl -fsS -m 10 https://hc-ping.com/your-uuid > /dev/null

This catches every failure mode including the server being off, which email-on-failure cannot.

Check it ran — grep CRON /var/log/syslog (Ubuntu) or journalctl -u cron shows each invocation with the command.

Locking

A job that takes longer than its interval starts overlapping itself — two backups writing the same file, two queue workers processing the same item. Wrap it in flock:

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

-n skips the run if the previous one still holds the lock.

systemd timers

On systemd servers, a timer unit does the same job with logging built into journalctl and no PATH surprises. It is more files for the same result; use it when you want the run history, cron when you want one line. Both are covered in systemd basics.

On a VPSPioneer managed VPS, we set up backup and maintenance jobs with the dead-man's-switch pattern above, so a job that stops running is something we hear about the same day.

#linux#cron#scheduling#automation

Keep reading

More from Linux

All guides

Linux

How to Add Swap Space to a VPS

Create a swap file on a Linux VPS in five commands, size it correctly, set swappiness so it is a safety net rather than a slowdown, and make it survive reboots.

2 min read →

Linux

How to Check Disk Space on Linux and Find What Is Filling It

df, du and ncdu to see where the space went, the usual culprits on a web server — logs, backups, caches, mail — and how to clear them safely.

3 min read →

Linux

systemd Basics: Start, Stop, Enable and Read Logs with journalctl

The systemctl commands that manage services on modern Linux, how to start a service at boot, write a unit for your own app, and read logs with journalctl.

3 min read →