crontab Command in Linux: Complete Guide to Scheduling Cron Jobs and Parameters

crontab command in Linux and it perimeters

crontab command in Linux and it perimeters

If there’s one Linux command that’s quietly running more of the internet than people realize, it’s crontab. Log rotation, database backups, certificate renewals, report generation, cache warming — a huge share of “boring but essential” server work runs on cron, and crontab is how you tell it what to do and when. I’ve relied on it for years to keep servers self-maintaining, and once you understand the time syntax and the daemon behind it, it becomes one of the most dependable tools in your admin toolkit.

What crontab Actually Is

crontab (short for “cron table”) is the command used to install, edit, list, and remove the schedule of jobs that the cron daemon (crond on RHEL-family systems, cron on Debian-family systems) executes automatically at specified times. Cron itself runs constantly in the background as a system service, waking up every minute to check whether any scheduled job matches the current time.

Each user on the system can have their own personal crontab file, and there’s also a system-wide crontab (/etc/crontab) plus drop-in directories (/etc/cron.d/) for packages and admins to install jobs without touching a single shared file.

Basic Syntax

crontab [-u user] file
crontab [-u user] [-l | -r | -e] [-i] [-s]

You almost never edit crontab files directly on disk — you use crontab -e, which validates syntax and handles locking safely.

The Five-Field Time Syntax

This is the part everyone has to memorize eventually:

* * * * * command-to-run
│ │ │ │ │
│ │ │ │ └── day of week (0–7, both 0 and 7 = Sunday)
│ │ │ └──── month (1–12)
│ │ └────── day of month (1–31)
│ └──────── hour (0–23)
└────────── minute (0–59)

An asterisk (*) means “every possible value for this field.”

Special Characters

Practical Examples

# Every minute
* * * * * /usr/local/bin/heartbeat.sh

# Every day at 2:30 AM
30 2 * * * /usr/local/bin/backup.sh

# Every 15 minutes
*/15 * * * * /usr/local/bin/check_disk.sh

# Every weekday (Mon–Fri) at 9 AM
0 9 * * 1-5 /usr/local/bin/morning_report.sh

# First day of every month at midnight
0 0 1 * * /usr/local/bin/monthly_billing.sh

# Every 6 hours
0 */6 * * * /usr/local/bin/sync_data.sh

# At system boot
@reboot /usr/local/bin/startup_tasks.sh

# Twice a day, at 6 AM and 6 PM
0 6,18 * * * /usr/local/bin/twice_daily.sh

Editing Your Crontab

crontab -e

The first time you run this as a given user, it may ask which editor to use:

no crontab for john - using an empty one

Select an editor.  To change later, run 'select-editor'.
  1. /bin/nano
  2. /usr/bin/vim.basic
  3. /bin/ed
Choose 1-3 [1]:

Once saved, cron validates the syntax before installing it — if there’s a malformed line, it’ll usually refuse to save and show you an error, which is one reason crontab -e is safer than hand-editing the underlying spool files.

Listing and Removing

crontab -l          # show your own crontab
crontab -u alice -l  # show alice's crontab (root only)
crontab -r          # delete your entire crontab (no confirmation!)
crontab -i -r        # delete with a confirmation prompt

crontab -r is genuinely dangerous — there’s no undo unless you kept a backup. I always run crontab -l > ~/crontab_backup_$(date +%F).txt before making risky changes.

Where Crontabs Actually Live

Personal crontabs (installed via crontab -e) are stored per-user, typically at:

/var/spool/cron/crontabs/<username>     # Debian/Ubuntu
/var/spool/cron/<username>              # RHEL/CentOS/Fedora

You should never edit these files directly with a text editor — always go through the crontab command, because it re-validates syntax and notifies the cron daemon of changes properly.

System-Wide Crontab and cron.d

Besides per-user crontabs, there’s /etc/crontab, which has one extra field compared to a personal crontab — the user to run the job as:

# /etc/crontab
# m h dom mon dow user  command
17 *    * * *   root    cd / && run-parts --report /etc/cron.hourly
25 6    * * *   root    test -x /usr/sbin/anacron || run-parts /etc/cron.daily

Packages typically install their own scheduled jobs as individual files under /etc/cron.d/, using this same six-field (with user) format, rather than touching /etc/crontab directly. This is the pattern you should follow for jobs that should be system-managed rather than tied to one user’s personal crontab:

sudo tee /etc/cron.d/backup-job <<'EOF'
# Nightly backup job
30 1 * * * root /usr/local/bin/nightly_backup.sh >> /var/log/nightly_backup.log 2>&1
EOF

There are also the convenience directories /etc/cron.hourly/, /etc/cron.daily/, /etc/cron.weekly/, /etc/cron.monthly/ — you drop an executable script in there (no crontab syntax needed) and run-parts (invoked from /etc/crontab or via anacron) executes everything inside on that cadence.

Controlling Who Can Use crontab

Two files gate access, checked in this order:

If neither file exists, behavior is distro-dependent — some allow everyone, some (notably Debian) restrict to root by default. Check with:

ls -la /etc/cron.allow /etc/cron.deny 2>/dev/null

How Cron Works Internally

The cron daemon starts at boot as a system service (systemctl status cron or systemctl status crond). Once running, it wakes up once per minute, reads every installed crontab (personal spool files plus /etc/crontab and /etc/cron.d/*), and compares the current minute/hour/day/month/weekday against each job’s schedule fields. Any job that matches gets forked and executed via /bin/sh (unless SHELL= is overridden inside the crontab), with output — both stdout and stderr — mailed to the crontab owner via the local mail system, unless redirected.

Cron does not reload crontab files continuously watching for changes in real time in older implementations, but modern cron daemons (like cronie, common on RHEL/Fedora) do detect modification timestamps on the spool directory and reload automatically — either way, crontab -e triggers an immediate reload as part of installing the new file, so you don’t need to restart the service after editing.

Environment Inside Cron Jobs

This is the single most common source of “it works when I run it manually, but not from cron” bugs. Cron jobs run with a very minimal environment — typically just:

SHELL=/bin/sh
PATH=/usr/bin:/bin
HOME=<user's home>
LOGNAME=<username>

Your ~/.bashrc, ~/.bash_profile, and any PATH modifications you rely on interactively are not loaded. This is why scripts that call python3, node, aws, or anything installed outside /usr/bin//bin mysteriously fail from cron.

Fixes:

# Explicitly set PATH at the top of the crontab
PATH=/usr/local/bin:/usr/bin:/bin

# Or set it inside the script itself
0 3 * * * /usr/local/bin/backup.sh
#!/bin/bash
# backup.sh
export PATH="/usr/local/bin:/usr/bin:/bin"
# ...

You can also set other variables at the top of a crontab, like MAILTO= (who gets the output mail, blank to disable) and SHELL=.

Real-World Examples and Shell Scripting Patterns

Database backup with logging and rotation:

0 2 * * * /usr/local/bin/pg_backup.sh >> /var/log/pg_backup.log 2>&1
#!/bin/bash
# pg_backup.sh
set -euo pipefail
BACKUP_DIR="/var/backups/postgres"
DATE=$(date +%F)
pg_dump mydb | gzip > "${BACKUP_DIR}/mydb_${DATE}.sql.gz"
find "$BACKUP_DIR" -name "*.sql.gz" -mtime +14 -delete

Preventing overlapping runs with flock:

*/5 * * * * /usr/bin/flock -n /tmp/sync.lock /usr/local/bin/sync_data.sh

This is essential for jobs that might occasionally run longer than their own interval — without a lock, you can end up with dozens of overlapping processes hammering the same resource.

Sending output only on failure instead of every run:

#!/bin/bash
OUTPUT=$(/usr/local/bin/some_task.sh 2>&1)
if [ $? -ne 0 ]; then
    echo "$OUTPUT" | mail -s "Cron job failed: some_task.sh" admin@example.com
fi

Troubleshooting

Job doesn’t run at all:

Job runs manually but fails from cron:

No output/errors visible:

Job seems to run twice:

Performance and Reliability Considerations

Comparison to Related Tools

crontab‘s five-field syntax and general behavior are consistent across Debian, Ubuntu, RHEL, CentOS, Fedora, SUSE, and Arch, since almost all of them ship either Vixie cron or its descendant cronie. Minor differences show up in default cron.allow/cron.deny behavior and in exact log file locations.

Summary

crontab is deceptively simple on the surface — five fields and a command — but the reliability of an entire server’s automation often rests on understanding its environment quirks, locking behavior, and where jobs actually live on disk. Get comfortable with crontab -e, always use absolute paths and explicit PATH settings, log everything, and use flock for anything that could overlap — and cron will quietly keep your systems running for years without complaint.

References

Exit mobile version