The gap between crond and WordPress
crond). It uses a virtual scheduler known as WP-Cron. Every time a visitor opens a page on your site, WordPress checks whether any scheduled tasks—such as publishing posts, checking for theme updates, or sending queued notification emails—are due. If tasks are pending, WordPress spawns an asynchronous HTTP request back to wp-cron.php to run them in the background.
On sites with steady traffic, this works reasonably well, although it adds latency to normal page visits. On low-traffic sites, scheduled tasks run late because nobody visits to trigger them. On high-traffic or heavily cached sites, edge caching often strips incoming requests before PHP executes, causing tasks to stall entirely. To fix this, administrators define DISABLE_WP_CRON as true in wp-config.php and set up an entry in the system crontab, expecting Linux to execute tasks on a predictable clock.
When this transition fails, the problem usually stems from a fundamental misunderstanding: the operating system cron daemon spawning a process does not guarantee that WordPress successfully ran its internal jobs. A shell command can trigger every ten minutes according to system logs, but if the command encounters a silent PHP fatal error, hits a timeout, or gets blocked by a security firewall, the internal queue remains frozen.
Step 1: Confirm that the system scheduler fires
/var/log/syslog or systemd journal units, whereas RHEL, Rocky Linux, AlmaLinux, and CentOS write directly to /var/log/cron.
Run a read-only search in your system logs to see if cron even attempts to run the task for your web user:
BASH
# For Debian/Ubuntu systems using journalctl
journalctl -u cron --since '1 hour ago' | grep -i 'wp-cron'
# For RHEL/Rocky Linux or standard log files
grep -i 'wp-cron' /var/log/cron | tail -n 20CRON[...] (username) CMD (...) matching your expected interval (such as every 10 or 15 minutes), the operating system scheduler is doing its part. However, a clean entry in the system cron log only confirms that the shell spawned the command. It does not tell you if that command exited with an error code, because standard crontab configurations silently discard standard output and standard error unless instructed otherwise.
Step 2: Test the invocation method directly
curl or wget, or directly through the PHP command-line interface (php /path/to/wp-cron.php or WP-CLI). Both methods can fail silently in different ways.
If your crontab uses curl or wget to request https://example.com/wp-cron.php, the request behaves like an external visitor. Run that exact command manually from your server shell without silent flags (such as removing -s or -q) and inspect the output. Often, you will immediately see an HTTP 403 Forbidden, 401 Unauthorized, or 429 Too Many Requests response.
External HTTP calls to wp-cron.php often fail because security plugins, Cloudflare rules, or web server configurations block requests without standard browser user-agents. Additionally, if your server cannot resolve its own domain name due to local DNS resolver misconfigurations, curl will hang until it hits a network timeout. When managing your own virtual server, issues like these frequently intersect with resource limits; as discussed in our look at managed WordPress hosting vs unmanaged VPS maintenance, self-managed infrastructure requires tracking both server-level networking and application behavior yourself.
If your crontab invokes PHP directly using the binary path, test it by running the command in your shell as the web user (for example, www-data, nginx, or your specific cPanel/control panel user account):
BASH
sudo -u www-data php -q /var/www/html/wp-cron.phpphp.ini file with a lower memory limit than your web server, the error message will print directly to your terminal. A common discrepancy is memory allocation: CLI PHP frequently defaults to different limits than PHP-FPM, causing memory-intensive tasks like image generation or automated backups to die instantly without leaving an entry in your web server access logs.
Step 3: Inspect the WordPress cron queue with WP-CLI
wp_options table under the option name cron.
Interpreting raw serialized arrays from the database is cumbersome and error-prone. The cleanest tool for inspecting this queue is WP-CLI, the official command-line interface for WordPress. Navigate to your site directory and inspect the scheduled hooks:
BASH
wp cron event list --path=/var/www/htmldoing_wp_cron containing a timestamp. This lock prevents concurrent cron jobs from running simultaneously and duplicating actions like charging payments or sending emails.
If a scheduled task encounters a fatal error, an unhandled exception, or a hard server process termination midway through execution, the script terminates before removing the doing_wp_cron lock. Subsequent cron invocations see the lock, assume another process is still actively working through the queue, and exit immediately without processing any hooks. The queue remains stuck until the lock times out, which by default takes several minutes, but can loop continuously if the same broken task crashes on every attempt.
You can pinpoint the broken task by running the due events individually through WP-CLI:
BASH
wp cron event run --due-now --path=/var/www/htmlEstablishing reliable cron execution
wp cron event run --due-now via system cron bypasses web server timeouts, avoids external HTTP firewalls entirely, and provides cleaner logging capabilities.
Verifying that scheduled tasks work properly is not a one-time check when launching a site. Any plugin update or change to server-side PHP limits can silently disrupt background execution. By checking the system logs first, running the invocation manually in the terminal second, and validating the internal event table with WP-CLI third, you can systematically diagnose any broken cron pipeline without relying on guesswork.