πŸ› οΈ ITBlogs.pro

Cron Jobs Explained: crontab Syntax, Examples and Troubleshooting

Oct 16, 2026 Β· Linux Β· 4 min read

Cron runs commands on a schedule: backups, cleanups, certificate checks, reports. It's simple, but its quirks cause most "my cron job doesn't work" problems.

Edit your crontab

crontab -e          # edit your jobs
crontab -l          # list them
sudo crontab -u www-data -e    # edit another user's crontab

The syntax

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€ minute (0-59)
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€ hour (0-23)
β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€ day of month (1-31)
β”‚ β”‚ β”‚ β”Œβ”€β”€β”€ month (1-12)
β”‚ β”‚ β”‚ β”‚ β”Œβ”€ day of week (0-7, 0 and 7 = Sunday)
β”‚ β”‚ β”‚ β”‚ β”‚
* * * * *  command
Schedule Expression
Every minute * * * * *
Every 5 minutes */5 * * * *
Every day at 03:15 15 3 * * *
Every Monday at 09:00 0 9 * * 1
1st of every month at midnight 0 0 1 * *
Weekdays at 08:30 30 8 * * 1-5
Twice a day (06:00 and 18:00) 0 6,18 * * *
At boot @reboot

Shortcuts also exist: @hourly, @daily, @weekly, @monthly. To check an expression, use crontab.guru.

A good cron line

15 3 * * * /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1
  • Use absolute paths for the command and for files it uses.
  • Redirect output (>> file 2>&1), otherwise it's mailed to the user or silently lost.
  • Make the script executable: chmod +x /usr/local/bin/backup.sh.

The #1 reason a job fails: the environment

Cron doesn't load your login profile. PATH is minimal (often just /usr/bin:/bin), so a command that works in your terminal fails with "command not found".

Fix it by setting variables at the top of the crontab:

SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
MAILTO=""

MAILTO="" disables the emails. Or just use full paths such as /usr/bin/php in the job.

Test what cron sees by simulating its bare environment:

env -i SHELL=/bin/sh PATH=/usr/bin:/bin /usr/local/bin/backup.sh

The % gotcha

In a crontab, an unescaped % means newline. A date command breaks:

# wrong: everything after the first % is cut off
0 2 * * * tar czf /backup/site-$(date +%F).tar.gz /var/www

# right: escape each % ...
0 2 * * * tar czf /backup/site-$(date +\%F).tar.gz /var/www

Or put the logic in a script file, where % needs no escaping. That's the cleaner option.

Prevent overlapping runs

If a job takes longer than its interval, copies pile up. Use flock:

*/5 * * * * flock -n /var/lock/sync.lock /usr/local/bin/sync.sh >> /var/log/sync.log 2>&1

-n makes it skip the run if the previous one still holds the lock.

System-wide jobs

Files in /etc/cron.d/ use the same syntax with an extra user column:

# /etc/cron.d/backup
15 3 * * * root /usr/local/sbin/server-backup >> /var/log/server-backup.log 2>&1

Scripts dropped into /etc/cron.daily/, /etc/cron.weekly/ and similar run automatically. They must be executable and the filename must contain no dot (so backup.sh is ignored, use backup).

Did it run? Check the logs

journalctl -u cron --since today          # Debian/Ubuntu
grep CRON /var/log/syslog | tail          # older style
sudo tail /var/log/cron                   # RHEL family

The log shows when a job started, not whether it succeeded, so your own log file (via the redirect) is the real source of truth.

More gotchas

  • Time zone: cron uses the server's time zone. Check with timedatectl. Around daylight-saving changes, a job at 02:30 may run twice or be skipped. Servers are happiest on UTC.
  • Day-of-month and day-of-week: if you set both, the job runs when either matches.
  • Permissions: the job runs as the crontab's owner. A file that user can't read or write causes silent failures.
  • No terminal: don't prompt for input or use tools that need a TTY.
  • Missed jobs: cron doesn't catch up if the machine was off. Use anacron or a systemd timer with Persistent=true for laptops and desktops.

Alternative: systemd timers

For anything that needs logging, dependencies, randomized delays or catch-up after downtime, systemd timers are a good fit. List them with:

systemctl list-timers

For simple scheduled scripts on a server, cron is still perfectly fine.

Monitoring tip

A job that silently stopped working is worse than one that fails loudly. Have the script ping a monitoring URL (like healthchecks.io) when it succeeds, so you're alerted when it doesn't.