cron — Time-Based Task Scheduler
By the end of this lesson you will be able to write a five-field cron expression, configure environment variables in a crontab header, redirect output to a log file, prevent job overlap with flock, schedule WordPress maintenance tasks, and debug a failing cron job from first principles.
Overview
cron is the standard time-based job scheduler on Unix and Linux systems. It runs commands automatically at fixed intervals — every minute, every night at 2 AM, every Monday, or once a month. On a WordPress VPS, you use cron to schedule backups, clear caches, rotate logs, run WP-CLI maintenance, and trigger monitoring checks. Cron is simple and universally available, but it has sharp edges: a minimal environment, no built-in output capture, and a syntax that is easy to misread. This page covers every feature of cron in detail so you can build reliable, production-safe scheduled jobs.
- Core Function: Execute shell commands at scheduled times defined by a five-field time expression.
- Primary Benefit: Universal availability — installed and active by default on every major Linux distribution.
- Where to Use: Any time-based automation on a Linux VPS — backups, WP-CLI tasks, log rotation, monitoring.
- Key Limitation: Minimal environment, no missed-run catch-up, no structured logging — requires explicit setup.
System Check
which crontab # Expected: /usr/bin/crontab
sudo systemctl status cron --no-pager --full # Verify cron service is running
crontab -l # List your current crontab (empty is fine)
How cron Works
Before writing any crontab entries, understand the five-field time model and the execution lifecycle:
- The crond daemon reads all crontab files every minute.
- It compares each job's five time fields to the current time.
- If all fields match, it executes the command in a minimal shell environment.
- Output goes nowhere unless you redirect it explicitly.
┌───────────── 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 sun-sat)
│ │ │ │ │
* * * * * command_to_execute
Lifecycle Diagram
The crond daemon is stateless — it does not remember whether previous runs succeeded or failed, and it will not catch up missed runs (e.g., if the server was down at 2 AM). For catch-up behavior, use anacron or systemd timer Persistent=true.
Execution Environment
Cron strips your interactive shell's environment before running any job. This is the most common source of "works on the terminal, fails in cron" bugs:
| What cron provides | What cron does NOT provide |
|---|---|
PATH=/usr/bin:/bin | Your full interactive $PATH |
HOME=/root (or user's home) | NVM, rbenv, pyenv, conda |
SHELL=/bin/sh | Your .bashrc / .profile aliases |
MAILTO=<crontab owner> | Any export from your ~/.bashrc |
Solution: Set PATH, SHELL, and any required variables at the top of the crontab, or use absolute paths for every command.
Where Cron Jobs Live
Cron jobs come from two categories:
User crontabs
Each user has a personal crontab edited with crontab -e. Jobs run as that user.
crontab -l # Your crontab
sudo crontab -l # Root's crontab
sudo crontab -u www-data -l # Another user's crontab
System cron files and directories
| Location | Format | Executed By |
|---|---|---|
/etc/crontab | Includes a user column | The cron daemon directly |
/etc/cron.d/* | Drop-in files, same format as /etc/crontab | The cron daemon directly |
/etc/cron.hourly/ | Executable scripts (no cron syntax) | run-parts via anacron or cron |
/etc/cron.daily/ | Executable scripts | run-parts |
/etc/cron.weekly/ | Executable scripts | run-parts |
/etc/cron.monthly/ | Executable scripts | run-parts |
sudo ls -lah /etc/cron.d/ 2>/dev/null || true
sudo ls -lah /etc/cron.hourly /etc/cron.daily /etc/cron.weekly /etc/cron.monthly 2>/dev/null || true
sudo run-parts --test /etc/cron.daily # Preview scripts that would run
Your First Cron Job in 5 Minutes
This hands-on walkthrough creates a trivial heartbeat job so you can see the full lifecycle before diving into reference material.
Step 1 — Create the script
The script defines what to run. Never put logic directly in the crontab.
sudo tee /usr/local/bin/heartbeat.sh > /dev/null << 'EOF'
#!/usr/bin/env bash
echo "[$(date +%F\ %T)] heartbeat" >> /tmp/heartbeat.log
EOF
sudo chmod +x /usr/local/bin/heartbeat.sh
Always make scripts executable with
chmod +xbefore referencing them in cron.
Step 2 — Test the script manually
Run it now so you know it works before adding it to cron:
/usr/local/bin/heartbeat.sh
cat /tmp/heartbeat.log
[2026-03-02 04:10:00] heartbeat
Step 3 — Open your crontab
crontab -e
Step 4 — Add the job entry
Paste this into the editor. The * * * * * means "every minute":
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
MAILTO=""
* * * * * /usr/local/bin/heartbeat.sh
Save and close. crontab -e validates syntax before saving.
Step 5 — Verify the entry was saved
crontab -l
Step 6 — Wait one minute and check the log
sleep 65 && cat /tmp/heartbeat.log
[2026-03-02 04:10:00] heartbeat
[2026-03-02 04:11:00] heartbeat
You now know the complete lifecycle: write script → test manually → edit crontab → verify → observe output. Every cron job you build follows the same pattern.
Syntax & Fields Reference
Core Syntax (user crontab)
# minute hour day-of-month month day-of-week command
15 2 * * * /usr/local/bin/wp-backup.sh
System crontab format (/etc/crontab and /etc/cron.d/*)
System crontab files include an extra user field between the time fields and the command:
# m h dom mon dow user command
15 2 * * * root /usr/local/bin/wp-backup-run
Field Value Reference
| Field | Allowed Values | Names Allowed |
|---|---|---|
| Minute | 0–59 | No |
| Hour | 0–23 | No |
| Day of month | 1–31 | No |
| Month | 1–12 | jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec |
| Day of week | 0–7 (0 and 7 = Sunday) | sun, mon, tue, wed, thu, fri, sat |
When both day-of-month and day-of-week are set (not *), the job runs when either condition is true — OR logic, not AND. This is one of the most common sources of unexpected cron schedules.
Operators
| Operator | Name | Meaning | Example | Translates to |
|---|---|---|---|---|
* | Wildcard | Every value in the field | * * * * * | Every minute |
*/n | Step | Every n-th value | */5 * * * * | Every 5 minutes (0, 5, 10…) |
a,b | List | Specific values | 0 9,17 * * * | At 9:00 and 17:00 |
a-b | Range | Consecutive values | 0 9-17 * * 1-5 | Every hour from 9-17, Mon-Fri |
a-b/n | Range + Step | Every n-th within range | 0 0-12/4 * * * | At hours 0, 4, 8, 12 |
Special Strings
These shortcuts replace the five time fields entirely:
| String | Equivalent | Meaning |
|---|---|---|
@reboot | (runs once at boot) | Run once when the system starts |
@yearly / @annually | 0 0 1 1 * | Once a year (Jan 1 midnight) |
@monthly | 0 0 1 * * | Once a month (1st day midnight) |
@weekly | 0 0 * * 0 | Once a week (Sunday midnight) |
@daily / @midnight | 0 0 * * * | Once a day (midnight) |
@hourly | 0 * * * * | Once an hour (minute 0) |
Crontab Environment Variables
You can set environment variables at the top of a crontab file. They apply to all entries below them.
| Variable | Purpose | Default | Example |
|---|---|---|---|
PATH | Search path for commands | /usr/bin:/bin | PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin |
SHELL | Shell used to run commands | /bin/sh | SHELL=/bin/bash |
MAILTO | Send job output to this address | User who owns the crontab | MAILTO=ops@example.com or MAILTO="" |
HOME | Working directory for jobs | User's home directory | HOME=/var/www/html |
CRON_TZ | Timezone for schedule evaluation | System timezone | CRON_TZ=UTC |
Recommended crontab header:
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
MAILTO=""
HOME=/var/www/html
# Jobs below this line use the above environment
crontab Command Reference
| Command | Description |
|---|---|
crontab -e | Edit the current user's crontab |
crontab -l | List the current user's crontab |
crontab -r | Remove the current user's entire crontab |
crontab -ri | Remove with confirmation prompt (safer) |
crontab -u USER -e | Edit another user's crontab (requires root) |
crontab -u USER -l | List another user's crontab (requires root) |
crontab FILE | Install a crontab from a file (replaces current) |
crontab -r is destructiveIt deletes your entire crontab instantly with no confirmation. On many keyboards, r is next to e. Consider aliasing crontab -r to crontab -ri to require confirmation.
Percent Sign (%) Gotcha
In crontab entries, % is interpreted as a newline. The first % ends the command; everything after it becomes stdin fed to the command. This commonly breaks date +%F patterns.
Failing example:
# BROKEN — the % splits the command
15 2 * * * /usr/local/bin/backup.sh >> /var/log/backup-$(date +%F).log 2>&1
Working solutions:
# Option 1: Escape the percent sign
15 2 * * * /usr/local/bin/backup.sh >> /var/log/backup-$(date +\%F).log 2>&1
# Option 2 (recommended): Move complexity into a script
15 2 * * * /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1
Output, Logging & MAILTO
Cron does not capture stdout or stderr by default. If output is produced and MAILTO is set (or defaults to the crontab owner), cron tries to email it. On most VPS environments, local mail is not configured, so output is silently lost.
Always redirect output to a log file:
15 2 * * * /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1
| Part | Meaning |
|---|---|
>> | Append stdout to the log file (never truncate) |
2>&1 | Redirect stderr into the same file as stdout |
MAILTO="" | Suppress any email attempt (set at top of crontab) |
Preventing Overlap with flock
If a job takes longer than the scheduling interval, cron launches another instance on top of it. This can cause double CPU/IO usage, corrupt backups, or create race conditions.
Use flock to ensure only one instance runs:
15 2 * * * flock -n /var/lock/backup.lock /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1
flock Flag | Meaning |
|---|---|
-n | Non-blocking — exit immediately if lock is held |
-w SECONDS | Wait up to SECONDS for the lock, then give up |
-E CODE | Exit with CODE if lock cannot be acquired (default is 1) |
Practical Examples
Minutes
1. Every minute (health check)
* * * * * /usr/local/bin/health-check.sh >> /var/log/health.log 2>&1
Schedule: * * * * * = every minute of every hour of every day.
Use case: Lightweight health pings or queue workers.
2. Every 5 minutes (disk monitoring)
*/5 * * * * /usr/local/bin/check-disk.sh >> /var/log/disk-check.log 2>&1
Schedule: */5 = at minutes 0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55.
Use case: Disk usage monitoring, cache age checks.
3. Every 15 minutes (WordPress cron runner)
*/15 * * * * cd /var/www/html && /usr/local/bin/wp cron event run --due-now --path=/var/www/html >> /var/log/wp-cron.log 2>&1
Schedule: */15 = minutes 0, 15, 30, 45.
Use case: Replace WordPress's default wp-cron.php page-visit trigger. Requires define('DISABLE_WP_CRON', true); in wp-config.php.
4. Every 30 minutes (CDN sync)
*/30 * * * * /usr/local/bin/sync-cdn.sh >> /var/log/cdn-sync.log 2>&1
Use case: CDN cache warm or asset sync.
5. Every 10 minutes during peak hours only
*/10 8-20 * * * /usr/local/bin/cache-warm.sh >> /var/log/cache-warm.log 2>&1
Schedule: every 10 minutes, but only from 8 AM to 8 PM. Use case: Keep the cache warm during peak traffic hours only.
Hours
6. Every hour (at minute 0)
0 * * * * /usr/local/bin/hourly-report.sh >> /var/log/hourly-report.log 2>&1
Use case: Hourly summary reports, log rotation checks.
7. Every 2 hours (media sync)
0 */2 * * * /usr/local/bin/sync-media.sh >> /var/log/media-sync.log 2>&1
Schedule: 0 */2 = minute 0 of hours 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22.
Use case: Sync uploaded media to offsite storage every 2 hours.
8. Every 6 hours (cache flush)
0 */6 * * * cd /var/www/html && /usr/local/bin/wp cache flush --path=/var/www/html >> /var/log/cache-flush.log 2>&1
Schedule: minute 0 of hours 0, 6, 12, 18. Use case: Periodically flush a stale or large object cache.
9. Business hours only — every hour 9-17, Mon-Fri
0 9-17 * * 1-5 /usr/local/bin/sync-crm.sh >> /var/log/crm-sync.log 2>&1
Use case: Sync WordPress orders with a CRM only during business hours.
Daily
10. Daily at 2:15 AM (WordPress backup with flock)
15 2 * * * flock -n /var/lock/wp-backup.lock /usr/local/bin/wp-backup.sh >> /var/log/wp-backup.log 2>&1
Use case: Nightly WordPress full backup during low-traffic hours. flock skips if a previous run is still going.
11. Daily WordPress database backup with flock
30 2 * * * flock -n /var/lock/wp-db-backup.lock /usr/local/bin/wp db export /mnt/backups/wp-db-$(date +\%F).sql --path=/var/www/html >> /var/log/wp-db-backup.log 2>&1
Schedule: daily at 2:30 AM. Note the escaped \%F.
Use case: Export the WordPress database to the backup volume.
12. Daily at midnight (log rotation)
0 0 * * * /usr/local/bin/log-rotate.sh >> /var/log/log-rotate.log 2>&1
13. Daily using @daily shortcut
@daily /usr/local/bin/cleanup-tmp.sh >> /var/log/cleanup.log 2>&1
Equivalent to 0 0 * * * (midnight).
14. Twice a day (9 AM and 9 PM)
0 9,21 * * * /usr/local/bin/uptime-check.sh >> /var/log/uptime.log 2>&1
15. Purge old backups (daily at 4 AM)
0 4 * * * find /mnt/backups -name "*.sql" -mtime +14 -delete >> /var/log/backup-purge.log 2>&1
Use case: Retention management — delete database backups older than 14 days.
16. Daily disk usage alert
*/10 * * * * /usr/local/bin/disk-alert.sh >> /var/log/disk-alert.log 2>&1
#!/usr/bin/env bash
set -euo pipefail
THRESHOLD=85
USAGE=$(df -h / | awk 'NR==2 {gsub(/%/,""); print $5}')
if [ "$USAGE" -ge "$THRESHOLD" ]; then
echo "[$(date -Is)] WARNING: Root disk at ${USAGE}%"
fi
Use case: Log a warning when root partition usage exceeds 85%.
17. Daily SSL certificate renewal check
0 3 * * * /usr/bin/certbot renew --quiet --deploy-hook "systemctl reload nginx" >> /var/log/certbot.log 2>&1
Use case: Certbot checks if any certificate is within 30 days of expiry and renews it. The deploy hook reloads Nginx only if a renewal happened.
Weekly
18. Every Monday at 3 AM (weekly cleanup)
0 3 * * 1 /usr/local/bin/weekly-cleanup.sh >> /var/log/weekly-cleanup.log 2>&1
Use case: Weekly WordPress plugin update check, old revision cleanup, wp db optimize.
19. Weekdays at 6 AM (business digest)
0 6 * * 1-5 /usr/local/bin/daily-digest.sh >> /var/log/digest.log 2>&1
Use case: Email a daily business digest on weekdays only.
20. Saturday maintenance window at 2 AM
0 2 * * 6 /usr/local/bin/maintenance.sh >> /var/log/maintenance.log 2>&1
Use case: Run heavier tasks (database optimization, full backup, filesystem check) on a weekend night.
21. Weekly PHP-FPM restart (reclaim memory)
0 5 * * 0 /usr/bin/systemctl restart php8.2-fpm >> /var/log/phpfpm-restart.log 2>&1
Schedule: Sunday at 5 AM. Use case: Reclaim leaked memory from PHP-FPM workers during the lowest traffic window.
Monthly and Beyond
22. 1st and 15th of every month
0 4 1,15 * * /usr/local/bin/bimonthly-report.sh >> /var/log/bimonthly.log 2>&1
Use case: Semi-monthly billing reports or archive rotation.
23. First of every month at midnight
0 0 1 * * /usr/local/bin/monthly-report.sh >> /var/log/monthly.log 2>&1
24. Once a year (January 1)
0 0 1 1 * /usr/local/bin/annual-cleanup.sh >> /var/log/annual.log 2>&1
Special
25. At reboot (one-time startup task)
@reboot /usr/local/bin/start-monitoring.sh >> /var/log/monitoring-boot.log 2>&1
Runs once, immediately after the system boots. Use case: Start a custom monitoring daemon or one-shot setup task after a reboot.
26. Nginx log rotation
0 0 * * * /usr/sbin/logrotate /etc/logrotate.d/nginx >> /var/log/logrotate-nginx.log 2>&1
27. Place in /etc/cron.d/ as a drop-in file
System-managed jobs can be placed in /etc/cron.d/ as files — no crontab -e needed:
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
MAILTO=""
# m h dom mon dow user command
15 2 * * * www-data flock -n /var/lock/wp-backup.lock /usr/local/bin/wp-backup.sh >> /var/log/wp-backup.log 2>&1
Safe Cron Script Template
Use a proper script for anything beyond a single command. This template includes every defensive pattern:
#!/usr/bin/env bash
set -euo pipefail
# Explicit environment
umask 077
export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
# Lock and log
LOCK="/var/lock/myjob.lock"
LOG="/var/log/myjob.log"
mkdir -p "$(dirname "$LOG")"
{
echo "[$(date -Is)] start"
echo "user=$(id -un) uid=$(id -u)"
echo "pwd=$(pwd)"
# Acquire lock (non-blocking)
flock -n "$LOCK" /usr/local/bin/myjob
echo "[$(date -Is)] done (exit $?)"
} >> "$LOG" 2>&1
Corresponding crontab entry:
15 2 * * * /usr/local/bin/myjob-wrapper.sh
Debugging Workflow
Step-by-step debug protocol
- Run the command manually — execute the exact command from the crontab:
/usr/local/bin/myjob.shecho "Exit code: $?"
- Verify absolute paths — replace every bare command with its full path.
- Simulate the cron environment — strip your interactive shell:
simulate-cron-environment.shenv -i HOME=$HOME SHELL=/bin/sh PATH=/usr/bin:/bin /usr/local/bin/myjob.shecho $?
- Check the output log:
tail -f /var/log/myjob.log
- Inspect cron's own execution log:
check-cron-logs.shjournalctl -u cron --since '2 hours ago' --no-pager | tail -20grep CRON /var/log/syslog | tail -20
Common failure patterns
| Symptom | Likely Cause | Fix |
|---|---|---|
| Job does not run at all | Cron service not running, or wrong crontab | systemctl status cron; verify with crontab -l |
| Job runs but "command not found" | Minimal PATH in cron | Use absolute paths or set PATH= at top of crontab |
| Job runs but no output file | Wrong redirect path, or directory doesn't exist | Create the log directory; test the redirect manually |
| Job runs differently than interactive | Missing environment variables | Set PATH, HOME, SHELL in crontab header or script |
| Job runs twice / overlaps | Schedule interval shorter than runtime | Add flock -n /var/lock/job.lock |
% breaks the command | Unescaped percent sign in crontab entry | Escape as \% or move the command into a script |
| Job fires at wrong time | Day-of-month AND day-of-week both set | Remember: OR logic — either condition fires the job |
| No evidence it ran at all | MAILTO issue masking error | Redirect to log file; set MAILTO="" in crontab |
WordPress VPS Cron Patterns
| Task | Schedule | Command Pattern |
|---|---|---|
| WordPress cron events | */15 * * * * | wp cron event run --due-now --path=/var/www/html |
| Nightly full backup | 15 2 * * * | backup.sh + flock |
| Database export | 30 2 * * * | wp db export /mnt/backups/db.sql + flock |
| Backup retention | 0 4 * * * | find /mnt/backups -mtime +14 -delete |
| Cache flush | 0 */6 * * * | wp cache flush |
| Disk usage alert | */10 * * * * | Script checking df -h output |
| SSL certificate renewal | 0 3 * * * | certbot renew --quiet |
| PHP-FPM restart | 0 5 * * 0 | systemctl restart php8.2-fpm |
| Log rotation | 0 0 * * * | logrotate /etc/logrotate.d/nginx |
Benefits
- Universal availability — installed and active by default on virtually every Linux distribution.
- Simple, proven model — five time fields plus a command; nothing more to learn to get started.
- Per-user isolation — each user has their own crontab; jobs run under that user's UID.
- System-level directories —
/etc/cron.d/,/etc/cron.daily/, etc. allow drop-in job management without editing a monolithic file. - Special strings —
@reboot,@daily,@hourlyetc. make common schedules readable at a glance. - Email notification — built-in
MAILTOvariable can send job output to an address when a mail subsystem is configured. - Low overhead — the cron daemon is lightweight and consumes negligible resources.
Best Practices
- Always use absolute paths — cron runs with a minimal
PATH(/usr/bin:/bin). Use/usr/local/bin/wpinstead ofwp. - Redirect output to a log — cron does not capture stdout/stderr by default. Always append
>> /var/log/jobname.log 2>&1. - Use
flockto prevent overlap — if a job takes longer than the interval, the next run starts on top of it. Useflock -n /var/lock/job.lockto skip the duplicate. - Edit with
crontab -e— never edit spool files directly;crontab -evalidates syntax before saving. - Set
PATHat the top of the crontab — one line at the top eliminates most "command not found" failures. - Escape
%signs — in crontab entries,%is interpreted as a newline. Escape as\%or move the command into a script. - Test the exact command manually first — run the command interactively in a stripped environment, confirm it works, then add it to cron.
- Use a script for anything non-trivial — keep the crontab entry short; put logic in a bash script with
set -euo pipefail. - Set
MAILTO=""— even if mail isn't configured, suppresses attempted mail delivery noise. - Monitor cron logs — check
journalctl -u cronor/var/log/syslogregularly to confirm jobs actually fire.
Tips & Strategy
Before adding a job, paste your five time fields into crontab.guru to see a plain-English translation. This catches off-by-one hour mistakes and ambiguous day-of-week values instantly.
Keep your crontab entries minimal:
15 2 * * * /usr/local/bin/wp-backup.sh >> /var/log/wp-backup.log 2>&1
Put all logic inside wp-backup.sh. This makes debugging, testing, and version control trivial.
15 2 * * * flock -n /var/lock/wp-backup.lock /usr/local/bin/wp-backup.sh >> /var/log/wp-backup.log 2>&1
If the previous run is still going, flock -n makes the new instance exit immediately instead of doubling CPU and I/O.
Cron strips your interactive shell's environment. To simulate:
env -i HOME=$HOME SHELL=/bin/sh PATH=/usr/bin:/bin /usr/local/bin/myjob.sh
If this fails but running the script normally works, you have an environment or PATH issue.
@reboot for one-time startup tasks@reboot /usr/local/bin/start-monitoring.sh >> /var/log/monitoring.log 2>&1
Runs once at system boot — useful for restarting custom daemons or one-shot setup scripts.
Hands-On Practice
Quick Lab (5 Exercises)
- Run
crontab -lto view your current crontab. If empty, add a test job that appends the current date to a file every minute. - Create a script at
/tmp/cron-test.shthat echoes[date] hello from cron. Schedule it to run every minute. Verify output appears in the log file. - Edit the crontab to set
PATH=at the top. Remove all absolute paths from one entry and confirm it still works. - Add
flock -n /tmp/cron-test.lockto the entry. Open two terminals and test that the second invocation exits without running. - After testing, remove the test entry with
crontab -e. Verify withcrontab -l.
Task: Build a Cron-Based Backup System
Write a complete crontab (with header variables) that:
- Runs a WordPress database backup at 2:30 AM daily
- Purges backups older than 14 days at 4 AM daily
- Checks disk usage every 10 minutes
- Flushes the WordPress object cache every 6 hours
Solution
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
MAILTO=""
HOME=/var/www/html
# WordPress database backup (daily at 2:30 AM, with lock)
30 2 * * * flock -n /var/lock/wp-db-backup.lock /usr/local/bin/wp db export /mnt/backups/wp-db-$(date +\%F).sql --path=/var/www/html >> /var/log/wp-db-backup.log 2>&1
# Purge old backups (daily at 4 AM)
0 4 * * * find /mnt/backups -name "*.sql" -mtime +14 -delete >> /var/log/backup-purge.log 2>&1
# Disk usage alert (every 10 minutes)
*/10 * * * * /usr/local/bin/disk-alert.sh >> /var/log/disk-alert.log 2>&1
# Flush WordPress object cache (every 6 hours)
0 */6 * * * cd /var/www/html && /usr/local/bin/wp cache flush --path=/var/www/html >> /var/log/cache-flush.log 2>&1
Mini Quiz
- What are the five time fields in a cron entry, in order?
- What does
*/5mean in the minute field? - What is the difference between a user crontab and
/etc/crontab? - Why should you always use absolute paths in cron jobs?
- What does
%do inside a crontab entry and how do you escape it? - How does
flock -nprevent overlapping jobs? - What does
@rebootdo? - How do you redirect both stdout and stderr to a log file?
- When both day-of-month and day-of-week are set, does cron use AND or OR logic?
- What command shows cron's own execution log on a systemd-based system?
Cheat Sheet
# ── Crontab management ────────────────────────────────────────────────────────
crontab -e # Edit your crontab
crontab -l # List your crontab
crontab -ri # Delete with confirmation (safer than -r)
sudo crontab -u www-data -e # Edit another user's crontab
sudo crontab -u www-data -l # List another user's crontab
# ── System cron locations ─────────────────────────────────────────────────────
ls /etc/cron.d/ # Drop-in cron files
sudo run-parts --test /etc/cron.daily # Preview daily jobs
# ── Cron service ──────────────────────────────────────────────────────────────
sudo systemctl status cron # Check cron daemon status
journalctl -u cron --since '1 hour ago' --no-pager # Execution log
grep CRON /var/log/syslog | tail -20 # Syslog-based systems
# ── Simulate cron environment ─────────────────────────────────────────────────
env -i HOME=$HOME SHELL=/bin/sh PATH=/usr/bin:/bin /usr/local/bin/myjob.sh
# ── Common schedule patterns ──────────────────────────────────────────────────
# * * * * * Every minute
# */5 * * * * Every 5 minutes
# */15 * * * * Every 15 minutes
# 0 * * * * Every hour (at :00)
# 0 */2 * * * Every 2 hours
# 0 0 * * * Daily at midnight
# 15 2 * * * Daily at 2:15 AM
# 0 9,17 * * * Twice a day (9 AM and 5 PM)
# 0 6 * * 1-5 Weekdays at 6 AM
# 0 3 * * 1 Every Monday at 3 AM
# 0 0 1 * * First of every month
# 0 0 1 1 * January 1st (yearly)
# @reboot Once at boot
# @daily Shortcut for midnight
# @hourly Shortcut for every hour
# ── Safe cron entry pattern ───────────────────────────────────────────────────
15 2 * * * flock -n /var/lock/job.lock /usr/local/bin/job.sh >> /var/log/job.log 2>&1