Linux Crontab Guide: Master Cron Scheduling in 10 Minutes
Cron is the backbone of automation on every Linux server. Whether you are scheduling database backups, log rotation, cache warming, or health checks, a solid understanding of crontab syntax will save you from silent failures and 3 AM pages.
Why Cron Jobs Fail Silently
Cron is one of those tools that looks simple but hides a surprising number of gotchas. A job works perfectly when you run it manually, then silently does nothing when scheduled. The root causes are almost always one of three things:
- Wrong PATH: Cron runs with a minimal
PATH=/usr/bin:/bin. Commands likenode,python3, orphpthat live in/usr/local/binare not found. - Missing environment: Cron does not source
.bashrc,.bash_profile, or.profile. Environment variables your script depends on are absent. - Output goes to mail: By default, any output (stdout and stderr) is emailed to the crontab owner via local mail. If no mail daemon is configured, output is silently discarded.
Understanding these three failure modes will help you write robust cron jobs from the start. Let's build up from the basics.
Crontab Syntax: The Five Fields
Every crontab line that schedules a job follows this format:
# ┌──────────── minute (0–59)
# │ ┌────────── hour (0–23)
# │ │ ┌──────── day of month (1–31)
# │ │ │ ┌────── month (1–12 or Jan–Dec)
# │ │ │ │ ┌──── day of week (0–7, 0 and 7 = Sunday, or Mon–Sun)
# │ │ │ │ │
# * * * * * command to run
Each field accepts:
*- any value (wildcard)5- exact value1-5- range (1 through 5)*/15- step (every 15 units)1,3,5- list (1, 3, and 5)
Real-World Crontab Examples
Here are the patterns you will actually use on a production server:
# Run a backup script every day at 2:30 AM
30 2 * * * /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1
# Run every 5 minutes
*/5 * * * * /opt/scripts/health-check.sh
# Run every weekday (Mon–Fri) at 8:00 AM
0 8 * * 1-5 /opt/scripts/daily-report.sh
# Run on the 1st of every month at midnight
0 0 1 * * /usr/local/bin/monthly-cleanup.sh
# Run every hour, on the hour
0 * * * * /opt/scripts/cache-warm.sh
# Run at 6 PM every Sunday
0 18 * * 0 /usr/local/bin/weekly-stats.sh
# Run every 15 minutes, but only in business hours (9–17)
*/15 9-17 * * 1-5 /opt/scripts/poll-api.sh
# Run twice a day - 6 AM and 6 PM
0 6,18 * * * /opt/scripts/sync.sh
Managing Your Crontab
The crontab command is your interface to scheduling. These are the essential operations:
crontab -e # Open your crontab in $EDITOR (default: vi)
crontab -l # List your current crontab
crontab -r # Remove your crontab entirely (careful - no confirmation)
crontab -u www-data -e # Edit crontab for another user (requires root/sudo)
crontab -u www-data -l # List crontab for another user
To change the default editor, set the EDITOR or VISUAL environment variable:
export EDITOR=nano
crontab -e
System-Wide Cron Files
In addition to per-user crontabs, Linux has system-wide cron locations:
/etc/crontab- System crontab with an extra username field/etc/cron.d/- Drop-in directory for system cron jobs (same format as/etc/crontab)/etc/cron.hourly/,/etc/cron.daily/,/etc/cron.weekly/,/etc/cron.monthly/- Place executable scripts here; they run at the named interval
# /etc/crontab format - includes username field
# min hour dom month dow user command
17 * * * * root cd / && run-parts --report /etc/cron.hourly
Files in /etc/cron.d/ must also include the username field, and must be owned by root with no write permission for others.
Special Cron Strings
Cron supports convenient shorthand strings that replace the five-field syntax:
@reboot # Run once at startup
@yearly # Equivalent to: 0 0 1 1 *
@annually # Same as @yearly
@monthly # Equivalent to: 0 0 1 * *
@weekly # Equivalent to: 0 0 * * 0
@daily # Equivalent to: 0 0 * * *
@midnight # Same as @daily
@hourly # Equivalent to: 0 * * * *
# Run a script on every server reboot
@reboot /usr/local/bin/start-services.sh
# Daily log rotation at midnight
@daily /usr/sbin/logrotate /etc/logrotate.conf
Build Cron Expressions Visually
Stop guessing cron syntax. Our free Crontab Generator lets you pick the schedule visually and get the exact crontab line to paste. No syntax errors, no surprises.
Open Crontab Generator →Fixing the PATH Problem
The most reliable way to fix PATH issues is to set it explicitly at the top of your crontab:
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
# Now node, npm, php, python3 etc. are all found
*/5 * * * * node /opt/app/scripts/monitor.js
Alternatively, use absolute paths for every command:
*/5 * * * * /usr/bin/php /var/www/html/cron.php
To find the absolute path of any command, run which command in your terminal, then hardcode that path in your crontab.
Environment Variables in Crontab
You can define environment variables directly at the top of your crontab, before any job entries:
PATH=/usr/local/bin:/usr/bin:/bin
MAILTO=ops@example.com
SHELL=/bin/bash
HOME=/var/www/html
# Variables are available to all jobs below
0 2 * * * /opt/scripts/backup.sh
Key variables and what they control:
- MAILTO - Email address for cron output. Set to empty string (
MAILTO="") to disable all email. - SHELL - Shell used to run commands. Defaults to
/bin/sh. - HOME - Home directory for the cron environment.
- PATH - Search path for executables.
Logging Cron Output
Never run a cron job without capturing output. Here are the patterns to use:
# Append stdout only - stderr still goes to mail
0 2 * * * /opt/scripts/backup.sh >> /var/log/backup.log
# Append both stdout and stderr to log
0 2 * * * /opt/scripts/backup.sh >> /var/log/backup.log 2>&1
# Separate stdout and stderr
0 2 * * * /opt/scripts/backup.sh >> /var/log/backup.log 2>> /var/log/backup-errors.log
# Timestamp every log line
0 2 * * * /opt/scripts/backup.sh | ts '%Y-%m-%d %H:%M:%S' >> /var/log/backup.log 2>&1
# Discard all output (not recommended - you lose visibility)
0 2 * * * /opt/scripts/backup.sh > /dev/null 2>&1
For high-frequency jobs, consider using logger to send output to syslog instead of a flat file. This integrates with log rotation automatically:
*/5 * * * * /opt/scripts/check.sh 2>&1 | logger -t my-cron-job
Then query with: grep my-cron-job /var/log/syslog
Viewing Cron Logs
To check whether cron jobs are actually running, look at the system log:
# Ubuntu / Debian
grep CRON /var/log/syslog
# CentOS / RHEL
grep CRON /var/log/cron
# Systemd-based systems
journalctl -u cron
journalctl -u crond # on CentOS/RHEL
# Follow cron log in real time
tail -f /var/log/syslog | grep CRON
A successful cron execution looks like this in syslog:
Mar 26 02:30:01 myhost CRON[12345]: (ubuntu) CMD (/usr/local/bin/backup.sh)
Preventing Overlapping Cron Jobs
If a cron job takes longer to run than its schedule interval, you can end up with multiple instances running simultaneously. This causes race conditions, database lock conflicts, and resource exhaustion. Use flock to prevent overlap:
# Only one instance of backup.sh runs at a time
*/5 * * * * flock -n /tmp/backup.lock /opt/scripts/backup.sh >> /var/log/backup.log 2>&1
The -n flag makes flock exit immediately (non-blocking) if the lock is already held. The second invocation simply exits rather than waiting.
Cron Security Considerations
Cron jobs run with the privileges of the user who owns the crontab. A few security rules to follow:
- Never run cron as root unless the task genuinely requires it. Use a dedicated service account.
- Set restrictive permissions on cron scripts:
chmod 750 /opt/scripts/backup.sh. - Validate inputs in scripts - cron output sent via email can be used for phishing if scripts echo user-controlled data.
- Use
/etc/cron.allowand/etc/cron.denyto control which users can create crontabs on shared systems. - Avoid storing credentials in crontab lines. Use environment files or a secrets manager instead.
Step-by-Step: Adding Your First Production Cron Job
- Write and test your script manually - run it from the terminal as the same user who will own the cron job and confirm it works.
- Use absolute paths everywhere in the script - for commands, input files, and output files.
- Add error handling - your script should exit with a non-zero code on failure.
- Open the crontab:
crontab -e - Set PATH at the top of the crontab if your script uses commands outside
/usr/bin:/bin. - Add the job line with output redirected to a log file:
0 2 * * * /opt/scripts/backup.sh >> /var/log/backup.log 2>&1 - Save and verify with
crontab -l. - Monitor the first execution - tail the log file and check syslog to confirm it ran.
Frequently Asked Questions
Why does my cron job work manually but not via cron?
The most common cause is PATH. Your login shell loads a rich PATH from .bashrc or .profile, but cron runs with PATH=/usr/bin:/bin. Commands like node, python3, php, and composer often live in /usr/local/bin and are not found. Fix it by adding PATH=/usr/local/bin:/usr/bin:/bin at the top of your crontab, or by using full absolute paths in your script.
How do I run a cron job every 10 seconds?
Cron's minimum interval is 1 minute. To run something more frequently, use a loop inside a script: start the script every minute via cron, and inside the script, run your task, sleep for 10 seconds, and repeat 6 times within that minute. Alternatively, consider using systemd timers which support second-level precision, or a process manager like Supervisor.
How do I stop cron from sending email for every job?
Add MAILTO="" at the top of your crontab to disable all mail. If you only want to suppress mail for specific jobs, redirect their output to a file: 0 * * * * /opt/script.sh > /dev/null 2>&1. Note that discarding all output makes debugging harder - logging to a file is preferable.
What is the difference between crontab -e and editing /etc/crontab?
crontab -e edits the current user's personal crontab, stored in /var/spool/cron/crontabs/. The jobs run as that user. /etc/crontab is a system-wide file that requires an extra username field in each job line, specifying which user runs the command. Files in /etc/cron.d/ use the same format as /etc/crontab.
How do I verify a crontab expression is correct before deploying?
Use our Crontab Generator or Cron Expression Parser to validate your schedule visually. Both tools show you exactly when the next N executions will occur, so you can confirm the timing before it runs in production.
Can I use cron on macOS?
Yes, macOS includes a cron daemon compatible with standard crontab syntax. However, Apple recommends using launchd (via .plist files in ~/Library/LaunchAgents/) for user-level scheduling. For server automation on macOS, cron works fine; for desktop automation, launchd offers more features including on-demand launching, throttling, and better integration with sleep/wake cycles.
Use our free tool here → Crontab Generator to build any cron expression visually without touching the syntax manually.
Usman has 10+ years of experience securing enterprise infrastructure, managing high-traffic servers, and building zero-knowledge security tools. Read more about the author.