How to schedule a job with cron¶
You have a command that should run on a schedule, for example a report every night at 02:30, and you want cron to run it.
This guide makes sure a cron daemon is installed and running, schedules the command as a system job in /etc/cron.d/ (or in your own crontab), tests it with a schedule of every minute, and then sets the real schedule.
The example job appends the disk usage of / to a log file.
Replace it with your own command.
On a systemd distribution, a systemd timer is an alternative with better logging and catch-up of missed runs; cron and systemd timers compares the two.
Before you begin¶
- You need root access through
sudo. - The command you schedule runs with a minimal environment: a short
PATH, no variables from your shell profile, and/bin/shas the shell. Use absolute paths in it.
Steps¶
-
Make sure a cron daemon is installed, enabled and running. Whether one is installed by default depends on the distribution and on how the system was installed.
Ubuntu server and cloud images include the
cronpackage (ISC cron); Debian's cloud images and minimal installations don't. Install it ifcommand -v crontabprints nothing, then check the service:On Debian,
cronrecommends a mail transfer agent, so this also installsexim4unless one is already present: cron mails the output of jobs to their owner. Add--no-install-recommendsto install cron alone; job output that isn't redirected is then discarded.The
croniepackage, withcronie-anacron, is installed and running by default. Make sure:Arch installs no cron daemon by default. Install cronie and enable its service:
Gentoo installs no cron daemon by default. (BusyBox's
busybox-crondOpenRC service, which thesys-apps/busyboxpackage installs, isn't enabled; this guide doesn't use it.) Install cronie, add its OpenRC service to thedefaultrunlevel and start it:sudo emerge --getbinpkg sys-process/cronie sudo rc-update add cronie default sudo rc-service cronie startOther daemons that provide
virtual/cron, such assys-process/fcronorsys-process/dcron, work too, but their service names, and some of their file formats, differ. With a systemd profile, enable cronie withsudo systemctl enable --now cronie.serviceinstead (systemd profile, not verified). -
Create the command the job runs,
/usr/local/bin/disk-usage-report:#!/bin/sh # disk-usage-report: print a timestamp and the disk usage of /. set -eu date -Iseconds df -h /Run it once by hand, so you know it works before cron runs it:
-
Schedule it as a system job, first with a schedule of every minute so you can see it run. Create
/etc/cron.d/disk-usage-report:# Disk usage report. Test schedule: every minute. SHELL=/bin/sh PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin MAILTO="" * * * * * root /usr/local/bin/disk-usage-report >> /var/log/disk-usage-report.log 2>&1- Files in
/etc/cron.d/have a sixth field, before the command, naming the user the job runs as (roothere). >> … 2>&1appends both output and errors to a log file, so you can see what the job did.MAILTO=""stops cron from trying to mail the output, since it's redirected anyway.- The file name must contain only letters, digits, hyphens and underscores: cron ignores files in
/etc/cron.d/with a dot in the name (such asreport.cron) on Debian and Ubuntu. - The file must be owned by root, must not be writable by group or others, and must end with a newline.
- cron starts the job at every scheduled time, even if the previous run is still going. If a run can take longer than the interval, make the command take a lock, as in How to write a safe shell script (step 6).
cron notices new and changed files in
/etc/cron.d/by itself; there's nothing to reload. - Files in
-
Wait until the next minute has passed, then check that the job ran:
$ sleep 65 $ cat /var/log/disk-usage-report.log 2026-11-07T14:22:01+00:00 Filesystem Size Used Avail Use% Mounted on /dev/vda1 9.7G 1.9G 7.4G 21% /cron also logs each job it starts:
$ sudo journalctl -u crond.service -n 20 --no-pager | grep disk-usage-report Nov 07 14:22:01 server1 CROND[5036]: (root) CMD (/usr/local/bin/disk-usage-report >> /var/log/disk-usage-report.log 2>&1) Nov 07 14:22:01 server1 CROND[5034]: (root) CMDEND (/usr/local/bin/disk-usage-report >> /var/log/disk-usage-report.log 2>&1)The same lines are written to
/var/log/cron.$ sudo journalctl -u cronie.service -n 20 --no-pager | grep disk-usage-report Nov 07 14:22:00 server1 CROND[1244]: (root) CMD (/usr/local/bin/disk-usage-report >> /var/log/disk-usage-report.log 2>&1) Nov 07 14:22:00 server1 CROND[1243]: (root) CMDEND (/usr/local/bin/disk-usage-report >> /var/log/disk-usage-report.log 2>&1)With sysklogd's default configuration, cron's messages go to
/var/log/syslog: -
Set the real schedule. Replace the five
*fields with the times you want, for example every day at 02:30:sudo sed -i 's|^\* \* \* \* \* root|30 2 * * * root|; s|Test schedule: every minute|Every day at 02:30|' /etc/cron.d/disk-usage-report$ grep root /etc/cron.d/disk-usage-report 30 2 * * * root /usr/local/bin/disk-usage-report >> /var/log/disk-usage-report.log 2>&1The fields are minute, hour, day of month, month and day of week; crontab syntax lists the values and shorthands such as
@daily. Times are in the system's time zone (timedatectlordateshows it).
Schedule a job as your own user¶
For a job that should run as you rather than as root, use your personal crontab, which has no user field.
Any user may have a crontab unless /etc/cron.allow or /etc/cron.deny says otherwise.
Any user not listed in /etc/cron.deny (empty by default) may have a crontab.
Any user not listed in /etc/cron.deny may have a crontab.
Edit your crontab with crontab -e, which opens it in the editor named by VISUAL or EDITOR, and installs it when you save and quit.
Add a line such as:
To add the same line from a script, without an editor, keep the existing entries and append the new one (|| true keeps a script running under set -e when you have no crontab yet):
{ crontab -l 2>/dev/null || true; echo '*/15 * * * * /usr/local/bin/disk-usage-report >> "$HOME/disk-usage-report.log" 2>&1'; } | crontab -
List it:
Test a user job the same way as a system job: temporarily use * * * * *, wait a minute, and check its log.
crontab -r removes your whole crontab without asking; use crontab -e to remove a single line.
Verify¶
- The cron daemon is running and enabled at boot:
systemctl is-enabled cron.service(crond.serviceon RHEL and its rebuilds,cronie.serviceon Arch), orrc-update show defaulton Gentoo, which listscronie. - Your job's log file grows at the scheduled times, and cron's log shows a
CMDline for each run.
Troubleshooting¶
The job never runs.
Check the file name and permissions in /etc/cron.d/, and look for a message about the file in cron's log:
- On Debian and Ubuntu (ISC cron), a name with a dot is ignored without a message. A file that is group- or world-writable is ignored with
INSECURE MODE (group/other writable). - With cronie (RHEL and its rebuilds, Arch, Gentoo), a file that is executable or group- or world-writable is ignored with
BAD FILE MODE. - A file not owned by root is ignored by both.
- A missing newline at the end of the last line makes ISC cron ignore the whole file (
Missing newline before EOF, this crontab file will be ignored); cronie skips only the last line (missing newline before EOF).
Make sure the cron daemon is running at all: on Debian cloud images and minimal installations, Arch and Gentoo, nothing schedules jobs until you install and start one.
cron logs the job, but it fails or does nothing.
The command runs with cron's environment, not your shell's.
Look at the log file the job writes to: command not found means a missing PATH entry, so set PATH= in the crontab or use absolute paths.
Commands that work only in your interactive shell (aliases, functions, variables from ~/.bashrc) aren't available.
A % in the command cuts it short.
cron treats an unescaped % as a newline, and passes everything after the first one to the command on standard input.
Write \% instead, for example date +\%F, or move the command into a script.
Jobs run at the wrong time.
cron uses the system time zone.
After changing the time zone, restart the daemon so that it picks up the change (sudo systemctl restart cron.service, crond.service or cronie.service, or sudo rc-service cronie restart).
Output disappears, or mail piles up for root.
Without MAILTO="" or a redirection, cron mails a job's output to its owner, or to MAILTO, if a mail transfer agent is installed.
cronie without a mail transfer agent sends the output to the system log instead.
On Gentoo, app-admin/sudo pulls in nullmailer, which only relays mail to another server: until you name one in /etc/nullmailer/remotes and start the nullmailer service, the mail waits in /var/spool/nullmailer/queue.
Redirect output to a file or to logger to keep it where you'll look.
Related¶
- Reference: crontab syntax
- Explanation: cron and systemd timers
- How-to: How to schedule a job with a systemd timer
- How-to: How to write a safe shell script
- Tutorial: Configure a server with Ansible
- Explanation: OpenRC and other init systems