Updated: 12 October 2026 · Applies to: cron 3.0pl1 on Debian 13, cronie 1.7.0 on AlmaLinux 10, systemd 257 timers and at 3.2.5 (all tested); the same commands work on Ubuntu and Rocky Linux
To schedule a recurring task in Linux, add a cron job: run crontab -e, add a line such as 0 3 * * * /usr/local/bin/backup.sh (every day at 03:00), save and exit. Cron reads the change within a minute; you do not need to restart it. For a task that should run only once, use at; for jobs that need proper logging or must catch up after downtime, use a systemd timer. All three are shown below.
Which scheduler to use
- cron: repeat a command on a fixed schedule (every 15 minutes, nightly, weekly). Installed on almost every server.
- systemd timers: the same job as a service, with its output in the journal and the option to run missed jobs after a reboot.
- at: run a command once at a later time. See the at command.
- anacron: daily or weekly jobs on machines that are not always on. See schedule jobs using anacron.
Add a cron job
- Open your crontab:
crontab -e. The first time it printsno crontab for root - using an empty oneand opens your editor (nano on a fresh Debian 13). UseEDITOR=nano crontab -eto choose another. - Add one line per job: five time fields, then the command. Use full paths:
0 3 * * * /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1
- Save and close. Check it:
crontab -l. - To add a job for another user, run as root:
crontab -u bob -e. The job then runs with bob's permissions.
In scripts, add a line without an editor: (crontab -l 2>/dev/null; echo '*/5 * * * * /usr/local/bin/check.sh') | crontab -. crontab checks the syntax and refuses a bad file: in our test a minute of 61 gave "-":0: bad minute and errors in crontab file, can't install. on Debian.
The five time fields
* * * * * command | | | | | | | | | +-- day of week (0 to 7, 0 or 7 = Sunday, or mon, tue...) | | | +---- month (1 to 12) | | +------ day of month (1 to 31) | +-------- hour (0 to 23) +---------- minute (0 to 59)
*/15 * * * *: every 15 minutes30 2 * * 1-5: 02:30 Monday to Friday0 0 1 * *: midnight on the first of each month0 9,17 * * *: 09:00 and 17:00 every day- Shortcuts instead of the five fields:
@reboot(once at startup),@hourly,@daily,@weekly,@monthly,@yearly.
According to the crontab manual, if you restrict both day of month and day of week, the job runs when either matches: 30 4 1,15 * 5 runs on the 1st, the 15th and every Friday. More examples are in how to read crontab syntax.
System-wide jobs: /etc/cron.d and the daily folders
Files in /etc/cron.d/ have a sixth field, the user to run as:
30 2 * * * root /usr/local/bin/backup.sh
On Debian and Ubuntu the file name must contain only letters, digits, underscores and hyphens. In our test, /etc/cron.d/myjob ran but /etc/cron.d/my.job was silently ignored; on AlmaLinux 10 both ran. To run a script daily, weekly or monthly without choosing a time, place it, executable, in /etc/cron.daily/ (or cron.weekly, cron.monthly); the same no-dots rule applies there on Debian and Ubuntu.
Why a cron job works in the terminal but not in cron
- A short PATH. Cron does not load your login shell. The environment of a root job in our test:
HOME=/root LOGNAME=root PATH=/usr/bin:/bin SHELL=/bin/sh
That is Debian 13; AlmaLinux 10 gavePATH=/usr/bin:/bin:/usr/sbin:/sbin. Use full paths (/usr/sbin/...) or setPATH=at the top of the crontab. - The % sign. In a crontab, an unescaped
%ends the command. The jobdate +%F >> /tmp/pct.logran as(root) CMD (date +)in the log and never created the file. Write\%, or move the command into a script. - Output and email. Output that you do not redirect is mailed to the crontab's owner. On our AlmaLinux 10 test server, which had no mail system, cronie logged it to the journal instead (
(root) CMDOUT (hello-from-cron)). Send output to a log file as in step 2 above, or setMAILTO="you@example.com"at the top of the crontab (MAILTO=""turns mail off). - Time zone. Cron uses the server's time zone. On AlmaLinux and Rocky Linux, crond keeps the old zone after you change it until you run
systemctl restart crond.
Check that a job ran in the journal: journalctl -u cron on Debian and Ubuntu, journalctl -u crond on AlmaLinux and Rocky Linux. Each run is logged as, for example, CRON[4475]: (root) CMD (date >> /tmp/cron-test.log). /var/log/cron exists only when rsyslog is installed. To see every job on a server, see list all cron jobs for all users.
Before big edits, keep a copy: crontab -l > ~/crontab-backup.txt (cronie on AlmaLinux 10 also saves the previous version in ~/.cache/crontab/crontab.bak each time you install a new one). crontab -r deletes the whole crontab without asking; crontab -ri asks first (crontab: really delete root's crontab? (y/n)).
Schedule a task with a systemd timer
- Create the service,
/etc/systemd/system/backup.service:[Unit] Description=Nightly backup [Service] Type=oneshot ExecStart=/usr/local/bin/backup.sh
- Create the timer with the same name,
/etc/systemd/system/backup.timer:[Unit] Description=Run backup.service every day at 03:00 [Timer] OnCalendar=*-*-* 03:00:00 Persistent=true [Install] WantedBy=timers.target
Persistent=trueruns the job at boot if the server was off at 03:00. - Enable it and check the next run:
$ sudo systemctl daemon-reload $ sudo systemctl enable --now backup.timer $ systemctl list-timers backup.timer NEXT LEFT LAST PASSED UNIT ACTIVATES Mon 2026-10-12 03:00:00 UTC 11h - - backup.timer backup.service
- Test the job now with
sudo systemctl start backup.serviceand read its output withjournalctl -u backup.service.
Check an OnCalendar expression before you use it:
$ systemd-analyze calendar "Mon..Fri 03:00"
Original form: Mon..Fri 03:00
Normalized form: Mon..Fri *-*-* 03:00:00
Next elapse: Mon 2026-10-12 03:00:00 UTC
Other useful forms: *:0/15 (every 15 minutes), daily, weekly (Monday 00:00).
Run a command once at a later time
$ echo "touch /tmp/at-test" | at now + 1 minute warning: commands will be executed using /bin/sh job 1 at Sun Oct 11 15:42:00 2026
According to the at manual, times such as now + 2 hours, 23:30 or 1am tomorrow work too. atq lists waiting jobs and atrm 1 removes one (package at, service atd). Without at, sudo systemd-run --on-active=30min /usr/local/bin/backup.sh creates a one-off timer.
On cPanel or Plesk hosting, add jobs in the control panel instead: cron jobs in cPanel, scheduled tasks in Plesk. To change existing jobs, see how to edit cron jobs.
Official documentation: crontab(5), systemd.timer(5).