Introduction
WordPress uses a pseudo-cron system (wp-cron.php) triggered by page visits to schedule tasks like publishing posts, sending emails, and running backups. Under high traffic, multiple wp-cron.php processes can spawn simultaneously, causing duplicate actions, high CPU usage, and database contention:
ps aux | grep wp-cron | wc -l
# Shows 20+ simultaneous wp-cron.php processesThis can result in users receiving duplicate notification emails, scheduled posts published multiple times, and backup plugins running overlapping jobs.
Symptoms
- Multiple wp-cron.php processes visible in
ps auxoutput - Duplicate emails sent from contact forms or notifications
- Scheduled posts published more than once
- High CPU usage during traffic spikes from concurrent cron execution
- Database lock errors from plugins trying to access the same resources
Common Causes
- Every page visit triggers wp-cron.php check, and high traffic spawns many instances
DISABLE_WP_CRONnot set to true, relying on visitor-triggered execution- Long-running cron tasks (backups, imports) overlap with new cron triggers
- No locking mechanism to prevent concurrent wp-cron.php execution
- Hosting environment with aggressive page caching still allows cron checks
Step-by-Step Fix
- 1.Disable visitor-triggered wp-cron in wp-config.php:
- 2.```php
- 3.define('DISABLE_WP_CRON', true);
- 4.
` - 5.Set up a system cron job to run wp-cron at controlled intervals:
- 6.```bash
- 7.crontab -e
- 8.# Add this line to run every 15 minutes
- 9.*/15 * * * * cd /var/www/html && /usr/bin/php wp-cron.php > /dev/null 2>&1
- 10.
` - 11.Use WP-CLI with locking to prevent concurrent execution:
- 12.```bash
- 13.*/15 * * * * flock -n /tmp/wp-cron.lock -c "cd /var/www/html && /usr/bin/wp cron event run --due-now > /dev/null 2>&1"
- 14.
` - 15.The
flockcommand ensures only one cron process runs at a time. - 16.Add PHP-level process locking as a fallback. Create a mu-plugin at
wp-content/mu-plugins/cron-lock.php: - 17.```php
- 18.<?php
- 19.// Prevent concurrent wp-cron.php execution
- 20.if (defined('DOING_CRON') && DOING_CRON) {
- 21.$lock_file = sys_get_temp_dir() . '/wp-cron-single.lock';
- 22.$lock_handle = fopen($lock_file, 'w');
- 23.if ($lock_handle && !flock($lock_handle, LOCK_EX | LOCK_NB)) {
- 24.error_log('wp-cron.php: Another instance is running, exiting.');
- 25.exit(0);
- 26.}
- 27.}
- 28.
` - 29.Identify and optimize long-running cron events:
- 30.```bash
- 31.cd /var/www/html
- 32.wp cron event list
- 33.
` - 34.Look for events with "now" status that indicate they are overdue. Reschedule heavy tasks to off-peak hours.
- 35.Verify the fix by monitoring cron processes:
- 36.```bash
- 37.# Run for 5 minutes during traffic
- 38.watch -n 1 'ps aux | grep wp-cron | grep -v grep | wc -l'
- 39.# Should show 0 or 1, never multiple
- 40.
`
Prevention
- Always use system cron instead of visitor-triggered wp-cron on production sites
- Monitor cron execution time and alert on events running longer than expected
- Use
flockin your system cron to prevent overlapping runs - Review scheduled events quarterly and remove unused hooks from deactivated plugins
- Configure WP-CLI cron to run during off-peak hours for resource-intensive tasks
- Add cron execution logging to track timing and identify problematic events:
- ```bash
- */15 * * * * flock -n /tmp/wp-cron.lock -c "cd /var/www/html && /usr/bin/wp cron event run --due-now >> /var/log/wp-cron.log 2>&1"
`