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:

bash
ps aux | grep wp-cron | wc -l
# Shows 20+ simultaneous wp-cron.php processes

This 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 aux output
  • 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_CRON not 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. 1.Disable visitor-triggered wp-cron in wp-config.php:
  2. 2.```php
  3. 3.define('DISABLE_WP_CRON', true);
  4. 4.`
  5. 5.Set up a system cron job to run wp-cron at controlled intervals:
  6. 6.```bash
  7. 7.crontab -e
  8. 8.# Add this line to run every 15 minutes
  9. 9.*/15 * * * * cd /var/www/html && /usr/bin/php wp-cron.php > /dev/null 2>&1
  10. 10.`
  11. 11.Use WP-CLI with locking to prevent concurrent execution:
  12. 12.```bash
  13. 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. 14.`
  15. 15.The flock command ensures only one cron process runs at a time.
  16. 16.Add PHP-level process locking as a fallback. Create a mu-plugin at wp-content/mu-plugins/cron-lock.php:
  17. 17.```php
  18. 18.<?php
  19. 19.// Prevent concurrent wp-cron.php execution
  20. 20.if (defined('DOING_CRON') && DOING_CRON) {
  21. 21.$lock_file = sys_get_temp_dir() . '/wp-cron-single.lock';
  22. 22.$lock_handle = fopen($lock_file, 'w');
  23. 23.if ($lock_handle && !flock($lock_handle, LOCK_EX | LOCK_NB)) {
  24. 24.error_log('wp-cron.php: Another instance is running, exiting.');
  25. 25.exit(0);
  26. 26.}
  27. 27.}
  28. 28.`
  29. 29.Identify and optimize long-running cron events:
  30. 30.```bash
  31. 31.cd /var/www/html
  32. 32.wp cron event list
  33. 33.`
  34. 34.Look for events with "now" status that indicate they are overdue. Reschedule heavy tasks to off-peak hours.
  35. 35.Verify the fix by monitoring cron processes:
  36. 36.```bash
  37. 37.# Run for 5 minutes during traffic
  38. 38.watch -n 1 'ps aux | grep wp-cron | grep -v grep | wc -l'
  39. 39.# Should show 0 or 1, never multiple
  40. 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 flock in 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"
  • `