Guide VPS & Servers

How to set up cron jobs on a Linux server

The five-field crontab syntax explained properly, plus the logging habit that stops a failed scheduled job from failing silently.

Updated 7 min read Beginner

Cron runs a command on a fixed schedule with no one needing to be logged in or watching — the standard way to automate a backup, a certificate renewal check, or any other task that needs to happen regularly without you remembering to trigger it manually.

Editing your crontab

crontab -e

This opens your personal crontab in the default editor. The first time, it may ask which editor to use — nano is the simplest choice if you are not sure. Each line in the file is one scheduled job. See what is currently scheduled for your account:

crontab -l

The five-field syntax

*  *  *  *  *  command-to-run
│  │  │  │  │
│  │  │  │  └── day of week (0-7, both 0 and 7 mean Sunday)
│  │  │  └───── month (1-12)
│  │  └──────── day of month (1-31)
│  └─────────── hour (0-23)
└────────────── minute (0-59)

An asterisk in any field means "every value of this field". Some worked examples:

ScheduleMeaning
0 3 * * *Every day at 3:00am
*/15 * * * *Every 15 minutes
0 */6 * * *Every 6 hours, on the hour
0 9 * * 1Every Monday at 9:00am
0 0 1 * *Midnight on the first day of every month
30 2 * * 0Every Sunday at 2:30am

A full example line, running a backup script daily at 3am:

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

Always use full paths

Cron runs with a minimal environment and none of the shell configuration or PATH additions your interactive login session has. A command that works perfectly when you type it directly can fail silently under cron simply because it cannot find the program you meant. Use the full path to both the interpreter and the script:

0 3 * * * /usr/bin/php /var/www/example.com/cron-task.php

Find the full path to any command with which:

which php
which mysqldump

Log the output, or you will never know it failed

By default, cron discards a job's normal output and only emails errors — and email delivery from a VPS is frequently not configured at all, which means that error simply vanishes. Always redirect a job's output to a log file explicitly:

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

>> appends rather than overwriting the log each run, and 2>&1 merges error output into the same stream so both successes and failures land in the same file. Check that file periodically, or better still, set your own alerting on it — a cron job that has been silently failing for weeks is one of the more common causes of "the backup did not actually exist when we needed it".

Test the exact command outside cron first

Run the full command, including its full paths, directly in your terminal before scheduling it. A script that works when you run it interactively but fails under cron is almost always an environment difference — a missing PATH entry, a relative path that resolved differently, or an environment variable your shell sets that cron does not.

Special shorthand strings

Cron also accepts a handful of readable shortcuts in place of the five fields, which some people find clearer for common cases:

@reboot   /usr/local/bin/startup-task.sh    # once, at system boot
@daily    /usr/local/bin/backup.sh          # equivalent to 0 0 * * *
@weekly   /usr/local/bin/weekly-report.sh   # equivalent to 0 0 * * 0
@hourly   /usr/local/bin/check.sh           # equivalent to 0 * * * *

@reboot in particular is genuinely useful for anything that needs to run once whenever the server restarts — re-establishing a state a service expects, for instance — rather than on any repeating schedule at all.

Root's crontab versus your own

crontab -e edits the crontab for whichever user runs it. A task needing root privileges — restarting a system service, writing to a directory only root can access — should go in root's crontab, edited with sudo crontab -e, rather than trying to run it as a normal user with sudo embedded inside the command line, which cron does not prompt for and will simply fail.

System-wide jobs can also be placed directly in /etc/cron.d/, which has one extra field compared to a user crontab — the user to run the command as — useful when a script needs to be deployed as part of a package or configuration management setup rather than a personal crontab.

Setting a mail recipient, if you want cron's built-in alerting

If outgoing mail is actually configured on the server, cron's default behaviour of emailing a job's output can be pointed somewhere useful by setting MAILTO at the top of the crontab:

MAILTO=you@example.com
0 3 * * * /usr/local/bin/backup.sh

If mail is not set up, rely on the log-file redirection approach above instead — it works regardless of whether the server can send email at all.

Where this comes up in practice

  • Running your backup script on a fixed nightly schedule.
  • Confirming certificate renewal on your own schedule, alongside or instead of certbot's own timer.
  • Rotating or cleaning up old log files that accumulate outside what logrotate already manages.
  • Any application-specific scheduled task — clearing a cache, sending a digest, checking for stale data — that your software needs run periodically.

Once a job is scheduled, confirm it actually ran at the expected time by checking its log file the next day, rather than assuming a correctly written crontab line is the end of the job.

Related reading