Automation is the backbone of effective server administration. Whether you need to run daily backups, rotate logs every week, clear temporary files, or check disk usage every hour, doing these tasks manually is tedious and error-prone. Linux cron jobs solve this problem by letting you schedule commands to run automatically at specific times, dates, or intervals — reliably and silently in the background.
This guide will take you from cron basics to advanced scheduling patterns, with practical examples for every common use case. By the end, you will be able to automate virtually any recurring task on your Linux server.
What Is Cron?
Cron is a time-based job scheduler built into virtually every Unix and Linux system. The cron daemon (crond or cron) runs continuously in the background and checks every minute whether any scheduled jobs need to be executed. Each user on a system can have their own crontab (cron table) — a file that lists commands and their schedules.
The name "cron" comes from the Greek word chronos, meaning time. It has been a core part of Unix systems since the 1970s, making it one of the most battle-tested tools in the Linux ecosystem. The modern implementation on most Ubuntu systems is provided by the cron package (Vixie cron), which adds features like per-user crontabs and environment variable support.
Understanding Crontab Syntax
The crontab file uses a specific five-field syntax to define when a job should run. Each line in a crontab represents one scheduled job and follows this format:
│ ┌───────────── hour (0 - 23)
│ │ ┌───────────── day of month (1 - 31)
│ │ │ ┌───────────── month (1 - 12)
│ │ │ │ ┌───────────── day of week (0 - 7, Sun=0 or 7)
│ │ │ │ │
* * * * * command_to_execute
| Field | Allowed Values | Special Characters | Example |
|---|---|---|---|
| Minute | 0-59 | * , - / | 30 = at minute 30 |
| Hour | 0-23 | * , - / | 14 = at 2 PM |
| Day of Month | 1-31 | * , - / | 1 = 1st of month |
| Month | 1-12 or JAN-DEC | * , - / | 6 = June |
| Day of Week | 0-7 or SUN-SAT | * , - / | 1 = Monday |
Special Characters Explained
* (Asterisk)
Matches every possible value. * * * * * means every minute of every hour of every day.
, (Comma)
Specifies a list of values. 1,15 in the day field means the 1st and 15th of the month.
- (Hyphen)
Defines a range. 9-17 in the hour field means every hour from 9 AM to 5 PM.
/ (Slash)
Specifies step values. */5 in the minute field means every 5 minutes.
Working with Crontab
Each user manages their crontab using the crontab command. Here are the essential operations:
$ crontab -e
# List your current cron jobs
$ crontab -l
# Remove all your cron jobs (use with caution!)
$ crontab -r
# Edit crontab for another user (requires root)
$ sudo crontab -u www-data -e
# List cron jobs for another user
$ sudo crontab -u www-data -l
The
-r flag removes your entire crontab instantly without confirmation. It is dangerously close to -e on the keyboard. Many admins add the EDITOR=nano environment variable and always review their crontab with crontab -l before making changes.
Special Scheduling Strings
Cron provides convenient shortcut strings for common schedules. These are easier to read and less error-prone than the five-field syntax:
| String | Equivalent | Description |
|---|---|---|
@reboot | Run at startup | Executes once when the system boots |
@yearly | 0 0 1 1 * | January 1st at midnight |
@monthly | 0 0 1 * * | First day of each month at midnight |
@weekly | 0 0 * * 0 | Every Sunday at midnight |
@daily | 0 0 * * * | Every day at midnight |
@hourly | 0 * * * * | At the start of every hour |
@reboot /usr/bin/node /home/app/server.js &
# Run daily database backup at midnight
@daily /home/scripts/backup-db.sh
# Renew SSL certificates monthly
@monthly /usr/bin/certbot renew --quiet
Practical Cron Job Examples
Let us look at real-world cron jobs you will commonly use on a production server. Each example includes the crontab entry and an explanation of when it runs.
Database Backups
0 2 * * * /usr/bin/mysqldump -u root --all-databases | gzip > /backups/mysql/db_$(date +\%Y\%m\%d).sql.gz
# Daily PostgreSQL backup at 2:30 AM
30 2 * * * /usr/bin/pg_dumpall -U postgres | gzip > /backups/pg/all_$(date +\%Y\%m\%d).sql.gz
# Weekly full backup on Sunday at 3:00 AM
0 3 * * 0 /home/scripts/full-backup.sh >> /var/log/backup.log 2>&1
In crontab, the
% character has special meaning (it creates a newline). You must escape it with a backslash (\%) when using it in commands like date +\%Y\%m\%d. This is one of the most common cron pitfalls and does not apply when running the same command manually in a terminal.
Log Rotation and Cleanup
0 4 * * 0 /usr/bin/find /var/log/app/ -name "*.log" -mtime +30 -delete
# Compress logs older than 7 days, daily at 3:30 AM
30 3 * * * /usr/bin/find /var/log/app/ -name "*.log" -mtime +7 ! -name "*.gz" -exec gzip {} \;
# Clear /tmp files older than 48 hours, every 6 hours
0 */6 * * * /usr/bin/find /tmp -type f -atime +2 -delete 2>/dev/null
Monitoring and Alerts
*/15 * * * * /home/scripts/check-disk.sh
# Monitor service health every 5 minutes
*/5 * * * * /usr/bin/systemctl is-active --quiet nginx || /usr/bin/systemctl restart nginx
# Send daily server report at 8:00 AM
0 8 * * * /home/scripts/daily-report.sh | mail -s "Server Report" [email protected]
Application Maintenance
* * * * * cd /var/www/myapp && /usr/bin/php artisan schedule:run >> /dev/null 2>&1
# Clear application cache daily at 4 AM
0 4 * * * cd /var/www/myapp && /usr/bin/php artisan cache:clear >> /dev/null 2>&1
# WordPress cron every 15 minutes (disable wp-cron.php)
*/15 * * * * cd /var/www/wordpress && /usr/bin/php wp-cron.php >> /dev/null 2>&1
# Renew SSL certificates twice daily
0 3,15 * * * /usr/bin/certbot renew --quiet --deploy-hook "systemctl reload nginx"
Handling Cron Output
By default, cron sends any output (stdout and stderr) from a job as an email to the user who owns the crontab. On most servers without a local mail transport agent, this means the output simply gets lost. Proper output handling is essential for debugging and auditing.
Redirecting Output to a Log File
0 2 * * * /home/scripts/backup.sh >> /var/log/cron-backup.log 2>&1
# Log stdout and stderr to separate files
0 2 * * * /home/scripts/backup.sh >> /var/log/backup.log 2>> /var/log/backup-error.log
# Discard all output (silent execution)
*/5 * * * * /home/scripts/healthcheck.sh > /dev/null 2>&1
# Keep errors only (discard normal output)
0 3 * * * /home/scripts/cleanup.sh > /dev/null
Using the MAILTO Variable
[email protected]
# Disable email notifications entirely
MAILTO=""
# Send to multiple recipients
[email protected],[email protected]
Debugging Cron Jobs
Cron jobs that work perfectly in a terminal but fail silently when scheduled are one of the most frustrating problems in Linux administration. Here are the most common causes and how to diagnose them.
Problem 1: PATH Issues
Cron runs with a minimal environment. The PATH variable is typically limited to /usr/bin:/bin, which means commands you normally use might not be found.
0 2 * * * mysqldump mydb > /backups/db.sql
# GOOD: Use absolute paths for ALL commands
0 2 * * * /usr/bin/mysqldump mydb > /backups/db.sql
# ALTERNATIVE: Set PATH at the top of your crontab
PATH=/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
0 2 * * * mysqldump mydb > /backups/db.sql
The number one reason cron jobs fail is missing PATH. Always use the full path to executables. You can find the full path of any command with
which command_name or type -a command_name.
Problem 2: Environment Variables
Cron does not load your shell profile (.bashrc, .profile, etc.). Any environment variables your script depends on will not be available.
SHELL=/bin/bash
PATH=/usr/local/bin:/usr/bin:/bin
HOME=/root
NODE_ENV=production
# Or source your profile in the command
0 3 * * * . /root/.profile && /home/scripts/deploy.sh
Problem 3: Permission Issues
$ ls -la /home/scripts/backup.sh
-rw-r--r-- 1 root root 1024 Mar 17 10:00 /home/scripts/backup.sh
# Make it executable
$ chmod +x /home/scripts/backup.sh
# Verify
$ ls -la /home/scripts/backup.sh
-rwxr-xr-x 1 root root 1024 Mar 17 10:00 /home/scripts/backup.sh
Checking the Cron Log
The system cron log records when jobs are started and if there were any errors loading crontabs.
$ grep CRON /var/log/syslog | tail -20
Mar 17 02:00:01 server CRON[12345]: (root) CMD (/home/scripts/backup.sh)
Mar 17 02:00:01 server CRON[12346]: (www-data) CMD (cd /var/www && php artisan schedule:run)
# Or use journalctl
$ journalctl -u cron --since "1 hour ago"
Environment Variables in Crontab
You can define environment variables at the top of your crontab. These apply to all jobs defined after them.
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
[email protected]
HOME=/root
# Database backups
0 2 * * * /home/scripts/backup-mysql.sh >> /var/log/cron/mysql-backup.log 2>&1
30 2 * * * /home/scripts/backup-postgres.sh >> /var/log/cron/pg-backup.log 2>&1
# Cleanup
0 4 * * 0 /home/scripts/cleanup-old-backups.sh >> /var/log/cron/cleanup.log 2>&1
# Monitoring
*/5 * * * * /home/scripts/healthcheck.sh > /dev/null 2>&1
System-Wide Cron Directories
In addition to per-user crontabs, Linux provides system-wide cron directories for different schedules:
| Directory | Schedule | Usage |
|---|---|---|
/etc/cron.d/ | Custom schedule | System-level cron jobs with user field |
/etc/cron.hourly/ | Every hour | Drop executable scripts here |
/etc/cron.daily/ | Once daily | Logrotate, man-db, etc. |
/etc/cron.weekly/ | Once weekly | fstrim, man-db rotation |
/etc/cron.monthly/ | Once monthly | Quota reports, cleanup tasks |
Files in
/etc/cron.d/ follow a slightly different format — they include a user field after the time specification: * * * * * root /path/to/command. Files in the hourly/daily/weekly/monthly directories are simply executable scripts with no special format. The system's anacron or run-parts runs them.
Cron vs Systemd Timers
Modern Linux distributions offer systemd timers as an alternative to cron. While both achieve the same goal, they have different strengths:
| Feature | Cron | Systemd Timers |
|---|---|---|
| Ease of setup | Simple | Moderate |
| Logging | Basic (syslog) | journalctl |
| Dependencies | None | After=, Requires= |
| Missed job handling | Skipped | Persistent=true |
| Sub-second precision | No (1 min minimum) | Yes |
| Resource control | None | cgroups, CPU/Memory limits |
| Universality | Every Unix/Linux | systemd only |
| Per-user support | Built-in | systemd --user |
For simple scheduled tasks, cron remains the easiest and most portable choice. Systemd timers are better when you need dependency management, persistent scheduling (catching up on missed runs), or fine-grained resource control. Many systems use both side by side.
Cron Security Best Practices
Cron jobs run with the privileges of the user who owns the crontab. This makes security an important consideration, especially for root cron jobs.
- Use absolute paths for all commands and scripts in crontabs
- Set restrictive permissions on cron scripts (700 or 750)
- Never store passwords directly in crontab entries — use config files with restricted permissions
- Use
/etc/cron.allowand/etc/cron.denyto control which users can create cron jobs - Log all cron job output to files for auditing
- Review crontabs regularly:
for user in $(cut -f1 -d: /etc/passwd); do crontab -u $user -l 2>/dev/null; done - Ensure scripts called by cron cannot be modified by unprivileged users
- Use lock files to prevent overlapping executions of long-running jobs
Preventing Overlapping Executions
If a cron job takes longer than its interval, multiple instances can run simultaneously, causing resource exhaustion or data corruption. Use flock to prevent this:
*/5 * * * * /usr/bin/flock -n /tmp/backup.lock /home/scripts/backup.sh
# With a timeout (wait up to 60 seconds for the lock)
*/5 * * * * /usr/bin/flock -w 60 /tmp/import.lock /home/scripts/import.sh
Quick Reference: Common Schedules
| Schedule | Crontab Expression | Description |
|---|---|---|
| Every minute | * * * * * | Runs 1,440 times per day |
| Every 5 minutes | */5 * * * * | Runs 288 times per day |
| Every 15 minutes | */15 * * * * | Runs 96 times per day |
| Every hour | 0 * * * * | At the start of each hour |
| Every 6 hours | 0 */6 * * * | At 00:00, 06:00, 12:00, 18:00 |
| Twice daily | 0 3,15 * * * | At 3:00 AM and 3:00 PM |
| Daily at 2 AM | 0 2 * * * | Once per day |
| Weekdays at 9 AM | 0 9 * * 1-5 | Monday through Friday |
| Weekends at noon | 0 12 * * 6,0 | Saturday and Sunday |
| First day of month | 0 0 1 * * | Midnight on the 1st |
| Every quarter | 0 0 1 1,4,7,10 * | Jan 1, Apr 1, Jul 1, Oct 1 |
| Last day of month | 0 0 28-31 * * [ $(date +\%d -d tomorrow) -eq 1 ] && | Requires conditional check |
Visual Cron Management with Panelica
While the command line gives you full control over cron jobs, managing them across multiple users on a shared server can become complex. Panelica includes a built-in cron job manager that provides a visual interface for creating and managing scheduled tasks.
With Panelica, you can create, edit, pause, and delete cron jobs through a web interface. Each user sees only their own jobs (enforced by RBAC), and administrators can manage jobs for any user. The visual scheduler eliminates syntax errors and provides human-readable descriptions of each schedule. Jobs can be paused without deleting them, and execution logs are available directly in the panel.
Cron is one of the most fundamental tools in a Linux administrator's toolkit. Master the five-field syntax, always use absolute paths, handle output properly, and use flock to prevent overlapping executions. Whether you manage cron through the command line or a visual interface, the ability to automate recurring tasks is what separates efficient server administration from constant manual intervention.