Skip to content
DevOps devops linux-admin 6 min read

Scheduling Tasks with cron

Most servers need to run jobs on a repeating schedule: back up a database every night, rotate logs every week, or clean out temporary files every hour. cron is the classic Linux tool for exactly this. It is a background service (a program that runs quietly all the time) that wakes up every minute, checks a list of scheduled jobs, and runs any whose time has come. On Ubuntu the cron service is installed and running by default, so you can start scheduling jobs right away.

What cron is and when to use it

A cron job is simply a command plus a schedule that says when to run it. The schedule and command live in a file called a crontab (short for “cron table”). cron reads these tables and fires off the commands at the right times — even while you are logged out, and even after a reboot.

Use cron when you want a command to run on a fixed clock-based schedule (every day at 2 AM, every Monday, every 15 minutes). Do not use cron for:

  • Long-running services that must stay up — use a systemd service instead.
  • Jobs that must survive missed runs (e.g. the laptop was off at 2 AM) — cron skips missed runs, but systemd timers can catch them up with Persistent=true.
  • Event-driven work (run when a file appears) — cron only understands time.

Check that cron is running:

systemctl status cron

Output:

● cron.service - Regular background program processing daemon
     Loaded: loaded (/lib/systemd/system/cron.service; enabled; preset: enabled)
     Active: active (running) since Mon 2026-06-15 09:12:04 UTC; 3 days ago

If it is not enabled, turn it on with sudo systemctl enable --now cron.

The five-field schedule syntax

Every cron schedule is five fields separated by spaces, followed by the command to run. The five fields, in order, are:

FieldMeaningAllowed values
1Minute0–59
2Hour (24-hour clock)0–23
3Day of month1–31
4Month1–12 (or Jan–Dec)
5Day of week0–7 (0 and 7 both mean Sunday, or Sun–Sat)

A few special characters make patterns easy:

SymbolMeansExampleReads as
*”every” value* * * * *every minute
,a list0 8,12,18 * * *at 08:00, 12:00, and 18:00
-a range0 9-17 * * *every hour from 09:00 to 17:00
/a step*/15 * * * *every 15 minutes

So 30 2 * * * means “at minute 30 of hour 2, every day” — i.e. 02:30 every night. And 0 0 * * 0 means “midnight on Sunday.”

There are also handy shortcuts you can use in place of the five fields: @daily (same as 0 0 * * *), @hourly, @weekly, @monthly, and @reboot (run once each time the machine boots).

Tip: Cron times follow the server’s system time zone, not yours. Check it with timedatectl. Many teams set servers to UTC so scheduled jobs are predictable across regions.

Editing your own crontab with crontab -e

Each user has their own crontab. To edit yours, run:

crontab -e

The first time, it asks which text editor to use — pick nano if you are unsure. Add a line at the bottom, then save and exit (in nano: Ctrl+O, Enter, then Ctrl+X). cron picks up the change immediately.

# Run a cleanup script every day at 3:15 AM
15 3 * * * /home/deploy/scripts/cleanup.sh

To see your current jobs without editing, use crontab -l. To wipe them all, use crontab -r (be careful — there is no undo).

Jobs in your user crontab run as you, with your permissions. If a job needs root, edit root’s crontab with sudo crontab -e rather than putting sudo inside the command.

System cron in /etc/cron.d

User crontabs are great for personal jobs, but for software you deploy it is cleaner to drop a file into /etc/cron.d/. These are system crontabs, and they have one extra field: a username that says who the job runs as. This keeps each job’s schedule in its own file, which is easy to manage with configuration tools and version control.

Create the file with sudo nano /etc/cron.d/db-backup:

# /etc/cron.d/db-backup
# m  h  dom mon dow  user     command
  0  2  *   *   *    postgres /usr/local/bin/db-backup.sh

The sixth column (postgres) is the user — that is the difference from a personal crontab.

Gotcha: Files in /etc/cron.d/ are ignored if the filename contains a dot (like db-backup.sh) or other odd characters. Use plain names with letters, digits, hyphens, and underscores only.

Ubuntu also ships drop-in directories /etc/cron.daily/, /etc/cron.weekly/, and so on. Any executable script placed there runs on that cadence — no schedule line needed.

A daily backup example

Let’s build a real nightly PostgreSQL backup. First the script:

sudo nano /usr/local/bin/db-backup.sh
#!/usr/bin/env bash
set -euo pipefail

BACKUP_DIR="/var/backups/postgres"
STAMP="$(/bin/date +%Y-%m-%d_%H%M)"
DB="appdb"

/bin/mkdir -p "$BACKUP_DIR"
/usr/bin/pg_dump "$DB" | /bin/gzip > "$BACKUP_DIR/${DB}_${STAMP}.sql.gz"

# Delete backups older than 14 days
/usr/bin/find "$BACKUP_DIR" -name '*.sql.gz' -mtime +14 -delete

Make it executable and schedule it:

sudo chmod +x /usr/local/bin/db-backup.sh
sudo nano /etc/cron.d/db-backup
# Nightly database backup at 02:00, logging output
0 2 * * * postgres /usr/local/bin/db-backup.sh >> /var/log/db-backup.log 2>&1

The >> /var/log/db-backup.log 2>&1 part sends both normal output and errors to a log file so you can debug failures later. Test the script manually first to be sure it works:

sudo -u postgres /usr/local/bin/db-backup.sh
ls -lh /var/backups/postgres

Output:

-rw-r--r-- 1 postgres postgres 4.2M Jun 15 02:00 appdb_2026-06-15_0200.sql.gz

Gotcha: cron has a minimal environment

This is the single most common reason cron jobs “work in my terminal but not in cron.” When you log in, your shell loads a rich environment — a long PATH (the list of folders Linux searches for commands), your HOME, and more. cron does not load any of that. It runs with a bare-bones environment and a tiny PATH (usually just /usr/bin:/bin).

The fix is simple: always use absolute paths for both your script and the commands inside it. Write /usr/bin/pg_dump, not pg_dump. Notice the backup script above uses /bin/date and /usr/bin/find, not bare names.

You can also set environment variables at the top of a crontab. cron understands plain NAME=value lines:

PATH=/usr/local/bin:/usr/bin:/bin
MAILTO=[email protected]

0 2 * * * postgres /usr/local/bin/db-backup.sh

Find the absolute path of any command with which:

which pg_dump

Output:

/usr/bin/pg_dump

Best Practices

  • Always use absolute paths for scripts and the commands they call — cron’s minimal environment will not find them otherwise.
  • Redirect output to a log file with >> /path/log 2>&1 so failures leave a trace you can read with journalctl or in /var/log.
  • Test the command by hand first (with sudo -u <user> if needed) before trusting the schedule.
  • Set servers to UTC so schedules are unambiguous across time zones; verify with timedatectl.
  • Use /etc/cron.d/ for deployed jobs so each job is a self-contained, version-controllable file with an explicit run-as user.
  • Avoid dots in /etc/cron.d/ filenames — cron silently skips them.
  • For jobs that must catch up after downtime, prefer systemd timers with Persistent=true over cron.
Last updated June 15, 2026
Was this helpful?