Skip to content

How to Schedule Tasks in Linux: Cron Jobs and Systemd Timers

Updated 6 min read

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

  1. Open your crontab: crontab -e. The first time it prints no crontab for root - using an empty one and opens your editor (nano on a fresh Debian 13). Use EDITOR=nano crontab -e to choose another.
  2. 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
  3. Save and close. Check it: crontab -l.
  4. 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 minutes
  • 30 2 * * 1-5: 02:30 Monday to Friday
  • 0 0 1 * *: midnight on the first of each month
  • 0 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

  1. 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 gave PATH=/usr/bin:/bin:/usr/sbin:/sbin. Use full paths (/usr/sbin/...) or set PATH= at the top of the crontab.
  2. The % sign. In a crontab, an unescaped % ends the command. The job date +%F >> /tmp/pct.log ran as (root) CMD (date +) in the log and never created the file. Write \%, or move the command into a script.
  3. 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 set MAILTO="you@example.com" at the top of the crontab (MAILTO="" turns mail off).
  4. 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

  1. Create the service, /etc/systemd/system/backup.service:
    [Unit]
    Description=Nightly backup
    
    [Service]
    Type=oneshot
    ExecStart=/usr/local/bin/backup.sh
  2. 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=true runs the job at boot if the server was off at 03:00.
  3. 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
  4. Test the job now with sudo systemctl start backup.service and read its output with journalctl -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).

Related services

KVM VPS hosting

NVMe SSD and full root access, with Free Basic Managed Support.

Unmanaged dedicated servers

full root access and free IPMI KVM, with Free Basic Managed Support.

Linux server management

hardening, patching, monitoring and automation for Ubuntu, Debian, RHEL and AlmaLinux.

Was this answer helpful?

Still stuck? Open a ticket