When WordPress Cron Dies Silently: Diagnosing and Fixing Scheduled Task Failures on Restricted Hosts

Your editorial calendar shows a post scheduled for 8:00 a.m. It’s now 8:45, and the post is still marked “Scheduled.” No error email. No dashboard warning. Just a missed deadline and a quiet failure. You log in, hit “Publish” manually, and the post goes live instantly. The problem isn’t the post. The problem is WordPress Cron—and your host has likely killed it without telling you.

This is a systems failure, not a content failure. For small-to-mid publishing teams running WordPress, the internal pseudo-cron system (WP-Cron) is a fragile dependency that breaks in specific, repeatable ways when a host disables loopback requests, restricts ALTERNATE_WP_CRON, or simply neuters the HTTP transport layer. The result: missed scheduled posts, stale cache purges, and plugin update checks that never fire. This article dissects the exact failure chain, shows you how to confirm the breakage, and gives you a durable fix that doesn’t rely on a hosting provider’s goodwill.

How WordPress Cron Actually Works (and Why It’s Not a Real Cron)

WordPress does not use a system-level cron daemon. Instead, it relies on a visitor-triggered HTTP request to /wp-cron.php. On every page load, WordPress checks a queue of scheduled events stored in the wp_options table under the cron option. If an event’s timestamp has passed, WordPress spawns a non-blocking HTTP request to itself to execute the hook. This is the spawn_cron() function in wp-includes/cron.php, which uses wp_remote_post() with a timeout of 0.01 seconds and blocking set to false.

The system is fragile by design. It depends on consistent site traffic to trigger the spawn. On a low-traffic editorial site—say, a niche publication with 500 daily visitors—cron may only fire when an editor happens to load a page. If the host disables loopback requests (a common security measure on shared or managed platforms), the wp_remote_post() call fails silently. The scheduled post sits in “missed schedule” purgatory. The editorial team blames WordPress. The real culprit is the hosting environment.

Confirming the Failure: Loopback Diagnostics and Cron Logging

Before you patch the system, you need proof. Start with a direct loopback test. Create a simple PHP file in your site root or use WP-CLI:

wp eval "
\$url = home_url('/wp-cron.php');
\$response = wp_remote_post( \$url, [ 'timeout' => 5, 'blocking' => true ] );
if ( is_wp_error( \$response ) ) {
    echo 'Loopback failed: ' . \$response->get_error_message();
} else {
    echo 'Loopback succeeded with status: ' . wp_remote_retrieve_response_code( \$response );
}
"

If you see a cURL error 7 (failed to connect) or a 403/404 status, your host is blocking the request. Many managed WordPress hosts disable loopbacks as a security measure, but they rarely document this. Next, check the cron queue directly:

wp cron event list

Look for events with a timestamp in the past. If you see a growing backlog of wp_scheduled_auto_draft_delete, wp_privacy_delete_old_export_files, or your own custom hooks, the internal cron system is not executing. The events are being queued but never spawned.

For a deeper trace, add a mu-plugin that logs cron spawn attempts:

add_action( 'init', function() {
    if ( isset(\$_GET['doing_wp_cron']) ) {
        error_log( 'WP-Cron triggered via HTTP at ' . current_time('mysql') );
    }
});

Check your error logs. If you see no entries after a scheduled post should have fired, the spawn request never reached your server. The failure is upstream.

The Real Fix: Bypassing WP-Cron Entirely with a System Cron Job

The only reliable solution for a production editorial site is to disable the internal pseudo-cron and invoke wp-cron.php via a genuine system cron job. This removes the dependency on site traffic and loopback HTTP requests. The trade-off is that you need access to the server’s crontab—something many managed hosts provide, but some restrict. If you’re on a host that locks down crontab, you’ll need to escalate or migrate. There is no reliable plugin-only workaround.

Step 1: Disable WP-Cron in wp-config.php

Add this line above the “That’s all, stop editing!” comment:

define('DISABLE_WP_CRON', true);

This prevents WordPress from attempting to spawn cron on every page load. It stops the failed loopback attempts and the associated performance penalty (each failed attempt can add 1–3 seconds to a page load, depending on timeout settings).

Step 2: Set Up a System Cron Job

Access your server’s crontab (via cPanel, Plesk, or SSH). Add an entry that directly executes wp-cron.php at your desired interval. For a news site publishing 5–10 articles daily, every 5 minutes is sufficient. For a high-frequency site, consider every 1–2 minutes. The command must use the server’s PHP binary, not a web request:

*/5 * * * * /usr/bin/php /path/to/your/site/wp-cron.php > /dev/null 2>&1

Critical details: Use the full path to the PHP binary (find it with which php). Use the full server path to wp-cron.php, not a URL. The > /dev/null 2>&1 suppresses output so cron doesn’t email you on every execution. If your host uses a custom PHP version (e.g., PHP 8.1 via cPanel), the path might be /opt/cpanel/ea-php81/root/usr/bin/php. Verify with your host’s documentation or support.

Step 3: Verify Execution and Timing

After setting the cron job, schedule a test post for 2 minutes in the future. Wait 5 minutes, then check if it published. If not, manually run the command via SSH to see errors:

/usr/bin/php /path/to/your/site/wp-cron.php

Common failures: PHP Fatal error: require_once(): Failed opening required 'wp-load.php'—this means the path to wp-cron.php is wrong or the working directory is incorrect. Fix by setting the cron job to run from the WordPress root:

*/5 * * * * cd /path/to/your/site && /usr/bin/php wp-cron.php > /dev/null 2>&1

Another failure mode: ALTERNATE_WP_CRON redirects. Some guides suggest defining ALTERNATE_WP_CRON as a workaround for loopback failures. This forces WordPress to use a redirect-based approach instead of a direct POST. It’s a bandage, not a fix. It still depends on HTTP transport and can fail under the same host restrictions. If you’re setting a system cron job, ensure ALTERNATE_WP_CRON is not defined—it’s unnecessary and can cause double-execution.

When Your Host Blocks All HTTP Requests from the Server

Some hosts (particularly those using containerized environments or aggressive mod_security rules) block outbound HTTP requests from the server entirely. In this case, even a system cron job that calls wp-cron.php via PHP will fail because wp-cron.php internally uses wp_remote_post() to spawn individual cron events. The spawn call is designed to be non-blocking, but if the HTTP transport is dead, the events never execute.

You can test this by running a manual HTTP request from the server:

curl -I https://your-site.com/wp-cron.php

If this returns a connection error or a 403, your host is blocking outbound HTTP. The workaround is to bypass the HTTP layer entirely and execute cron events directly via WP-CLI:

*/5 * * * * cd /path/to/your/site && wp cron event run --due-now > /dev/null 2>&1

This command uses WP-CLI to process all due cron events in a single, server-side execution. It’s more efficient than hitting wp-cron.php because it doesn’t spawn separate HTTP requests for each event. However, it requires WP-CLI to be installed and functional on the server—something not all managed hosts support. If WP-CLI is unavailable, you’re left with the direct PHP call to wp-cron.php as the next-best option.

Why This Matters for Editorial Workflows

When cron fails, the editorial team loses trust in the CMS. Writers schedule posts and they don’t go live. Editors set revision reminders and they never arrive. The site’s internal consistency decays: stale transients pile up, backup plugins skip cycles, and cache purges tied to cron hooks leave outdated content visible. For a small-to-mid publishing team, this isn’t just a technical annoyance—it’s a direct hit to credibility. Readers see a post dated yesterday that just appeared in their feed. The team starts manually publishing everything, which defeats the purpose of a scheduled editorial calendar.

This is also a governance issue. If your team relies on WordPress’s built-in scheduling, you’re implicitly trusting your host to support loopback requests. Most shared and managed WordPress hosts do not guarantee this. They disable it for security, then fail to document the impact. The editorial team doesn’t know why posts miss schedule; the technical team may not even be aware of the dependency. A single define('DISABLE_WP_CRON', true); and a crontab entry removes that hidden dependency and makes the system’s behavior explicit and auditable.

Monitoring Cron Health Over Time

After migrating to a system cron, you need visibility. Install a cron management plugin like WP Crontrol to view all registered cron events and their schedules. Check the “Next Run” column regularly. If you see events piling up with past-due timestamps, your system cron job has failed or been removed. This can happen silently during server migrations, PHP version changes, or control panel updates.

Set up a secondary monitor: a simple script that checks if a known cron event (like wp_scheduled_delete) has run within the last 24 hours. If not, it sends an alert. This can be a standalone PHP file invoked by the same system cron, or a health-check endpoint monitored by an external service like UptimeRobot. The key is to not rely on WordPress’s internal Site Health tool—it only reports if WP-Cron is disabled, not if the system cron job is actually executing events.

Handling Long-Running Cron Tasks

Some editorial workflows involve heavy cron tasks: generating PDF editions, syncing with third-party distribution APIs, or processing large XML sitemaps. When you switch to a system cron, these tasks run synchronously within the cron execution window. If a task takes 4 minutes and your cron interval is 5 minutes, you’re fine. If it takes 12 minutes, you risk overlapping executions and resource exhaustion.

Mitigate this by locking. WordPress core uses a locking mechanism for some tasks, but custom plugins often don’t. You can implement a simple file-based lock in your system cron command:

*/5 * * * * cd /path/to/your/site && flock -n /tmp/wp-cron.lock /usr/bin/php wp-cron.php > /dev/null 2>&1

The flock command ensures only one instance of wp-cron.php runs at a time. If a previous execution is still running, the new one exits immediately. This prevents the “thundering herd” problem where multiple cron executions pile up and consume all available PHP workers, taking the site offline.

FAQ: WordPress Cron Failures on Restricted Hosts

Why do scheduled posts miss their publish time even though the site has traffic?

WordPress’s internal cron system requires an HTTP request to /wp-cron.php to trigger scheduled events. If your host blocks loopback requests—HTTP calls from the server to itself—the trigger never fires, regardless of traffic. The events sit in the queue with past-due timestamps until someone visits the admin dashboard and manually triggers them, or until a plugin forces a cron spawn. This is a host-level restriction, not a WordPress bug.

Can I fix this without touching server configuration?

Not reliably. Some plugins attempt to replace WP-Cron with a third-party ping service, but this introduces an external dependency and potential security risk. The only durable fix is to disable WP-Cron and set up a system cron job. If your host doesn’t provide crontab access, you’ll need to escalate to their support or consider a host that does. This is a non-negotiable requirement for any production editorial site.

What’s the difference between wp cron event run and hitting wp-cron.php?

wp cron event run --due-now executes all due cron events directly in the current PHP process, without spawning separate HTTP requests. Hitting wp-cron.php via a web request spawns each event as a separate non-blocking HTTP call. The WP-CLI method is more efficient and avoids loopback issues entirely, but it requires WP-CLI to be installed. The wp-cron.php method is more portable but still vulnerable to HTTP transport failures if the host blocks outbound connections.

How do I know if my host blocks loopback requests?

Run the loopback test described earlier. If you get a connection error or a 403/404, loopbacks are blocked. You can also check your host’s documentation or ask their support directly: “Do you allow WordPress loopback requests from the server to itself?” Many support teams won’t understand the question, so be prepared to explain that it’s an HTTP request from the server to its own domain. If they confirm it’s blocked, ask if they provide crontab access as an alternative.

Next Steps for Your Editorial Infrastructure

Once you’ve stabilized cron, audit the rest of your site’s silent failure points. A common companion issue is permalink structure corruption after a migration or plugin conflict, which can cause “Nothing Found” errors on published content. If you’ve seen that, the diagnostic approach is similar: isolate the rewrite rules, test with a default theme, and rebuild the .htaccess structure. Read What to Fix First When a New WordPress Site Says Nothing Found for the exact sequence.

For teams managing multiple sites, consider standardizing your cron configuration as part of your site provisioning checklist. A single mu-plugin that logs cron execution, combined with a monitoring endpoint, can prevent the “silent failure” scenario across your entire portfolio. The goal is to make scheduled tasks as reliable as your editorial calendar demands—no surprises, no missed deadlines, no trust erosion.

Server rack with blinking lights, representing hosting infrastructure where cron jobs execute

Close-up of a laptop screen showing code and terminal commands for debugging WordPress

Person working at a desk with multiple monitors, managing editorial workflows and server tasks