Cron is a time-based job scheduler on Unix systems. It is how you schedule things to run automatically — database maintenance, backups, cache clearing, WordPress scheduled posts. Most sites that have mysterious problems with scheduled tasks are running into cron configuration issues.
WP cron: WordPress’s built-in scheduler
WordPress has its own cron system. It runs when someone visits the site: WordPress checks if any scheduled tasks are due and runs them. This works, but has problems:
- If your site has no traffic, scheduled tasks do not run
- Traffic spikes can cause scheduled tasks to pile up and run simultaneously
- It is not real cron — it is a rough approximation
For low-traffic sites or basic scheduled posts, WP cron is fine. For anything business-critical, replace it with system cron.
System cron: real time-based scheduling
System cron runs independently of web traffic. You define schedules in the crontab file:
* * * * * /path/to/command
│ │ │ │ │
│ │ │ │ └── Day of week (0-7, 0 and 7 are Sunday)
│ │ │ └──── Month (1-12)
│ │ └────── Day of month (1-31)
│ └──────── Hour (0-23)
└────────── Minute (0-59)
Examples:
# Run every 5 minutes
*/5 * * * * /path/to/command
# Run every day at 3am
0 3 * * * /path/to/command
# Run every Monday at 6am
0 6 * * 1 /path/to/command
# Run on the 1st of every month at midnight
0 0 1 * * /path/to/command
Setting up WP-CLI cron through system cron
Replace WP cron with a real cron job:
# Edit the crontab
crontab -e
# Add: run WP cron every 5 minutes
*/5 * * * * /usr/local/bin/wp cron event run --due-now --path=/var/www/example.com/public_html >> /var/log/wp-cron.log 2>&1
This actually runs the WP cron events on a real schedule, independent of traffic.
Common cron job mistakes
1. Missing PATH variable
Cron runs with a minimal environment. If your command uses wp or php, use the full path:
# Wrong
* * * * * wp cron event run --due-now
# Correct
* * * * * /usr/local/bin/wp cron event run --due-now --path=/var/www/html
2. Relative paths
Always use absolute paths in cron commands. The working directory is not guaranteed.
3. Not redirecting output
Cron output goes to email or is discarded. Redirect to a log file so you can debug failures:
*/5 * * * * /path/to/script.sh >> /var/log/script.log 2>&1
4. Running too frequently
If a job takes 60 seconds to run and you schedule it every minute, you get overlapping runs. Check the expected duration before setting the schedule.
Debugging cron problems
Check if cron is running
# List current crontab
crontab -l
# Check cron daemon is running
systemctl status cron # Debian/Ubuntu
systemctl status crond # CentOS/RHEL
ps aux | grep cron
Check job execution logs
# Debian/Ubuntu
grep CRON /var/log/syslog
# CentOS/RHEL
grep CRON /var/log/cron
Test a job manually first
Run the exact command from the crontab manually before scheduling it. If it fails interactively, it will fail in cron too.
/usr/local/bin/wp cron event run --due-now --path=/var/www/html
WP cron debugging
WordPress stores scheduled events in the wp_options table under cron and cron_array. To see what is scheduled:
wp cron event list
wp cron event list --due-now
To run a specific hook:
wp cron event run my_custom_hook
Cron job for WordPress database backup
A practical example — backup the WordPress database daily at 3am:
# Crontab entry
0 3 * * * /usr/local/bin/wp db export /backups/$(date +\%Y-\%m-\%d)-backup.sql --path=/var/www/html >> /var/log/wp-backup.log 2>&1
For a proper backup strategy (retaining the last 7 days):
#!/bin/bash
# /usr/local/bin/wordpress-backup.sh
DATE=$(date +%Y-%m-%d)
BACKUP_DIR=/backups
WP_PATH=/var/www/html
# Export database
/usr/local/bin/wp db export $BACKUP_DIR/$DATE.sql --path=$WP_PATH
# Keep only last 7 days
find $BACKUP_DIR -name "*.sql" -mtime +7 -delete
Scheduling with WP-CLI for non-WP tasks
WP-CLI is not just for WordPress. You can use it to run arbitrary PHP or shell scripts on a schedule:
# Run a custom PHP script
*/15 * * * * /usr/local/bin/wp eval 'include "/var/www/html/custom-script.php";'
Or trigger WP-CLI commands from cron:
# Clear expired transients every hour
0 * * * * /usr/local/bin/wp transient delete-expired --path=/var/www/html >> /var/log/wp-transients.log 2>&1