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