Every night, like clockwork, WordPress sites can mysteriously go offline on shared hosting accounts. The outage often happens at the exact same minute each day, lasts just long enough to frustrate visitors, and then clears up without leaving a trace. For many site owners, the culprit is invisible — buried inside WordPress’s default “pseudo-cron” system. Understanding why this happens and how to fix it is essential for anyone running multiple sites under one hosting account. I just had a Client whom hosted 11 sites on an Enterprise Cloud and had to get them on track, so I figured I would write about it here. This primarily deals with shared hosting, but the rule of thumb is the same on bare blade servers you just code it up if you don’t spring for the cPanel GUI.
Why WordPress Cron is Different
Unlike a traditional server cron job, WordPress doesn’t schedule tasks using the host’s built-in scheduler. Instead, it runs a script called wp-cron.php every time someone visits a page. That script checks WordPress’s internal schedule and runs any tasks that are due. Plugin developers often define their cleanup routines, update checks, and reporting jobs to run “daily” at midnight. On shared hosting, the server clock is almost always set to UTC. That means 0000 UTC/Z is 2000 Eastern, 1700 Pacific.
The result? At 0000 UTC/Z, every site and every plugin thinks it’s time to run its daily jobs. Image optimizers purge caches, backup plugins archive databases, analytics tools sync data, membership plugins check expiration dates. Multiply that across three, five, or ten WordPress installs sharing the same hosting account, and the server suddenly has to process dozens of simultaneous cron jobs. On shared hosting, resource limits are tight: a maximum number of processes, memory caps, and CPU throttling are strictly enforced. When the account exceeds those limits, the provider suspends activity until usage falls back under the threshold. That’s why so many site owners see five-minute outages at the exact same time every day.
The First Step: Disable WordPress Pseudo-Cron
The fix begins with disabling WordPress’s built-in pseudo-cron. In each site’s wp-config.php, add the following line just above the “stop editing” notice:
This doesn’t erase scheduled events. It simply prevents WordPress from checking them on every page load. That means cron jobs will no longer fire unpredictably when visitors hit the site. Instead, they’ll wait for you to trigger them through the server’s real cron scheduler.
The Second Step: Add Real Cron Jobs
In cPanel, under Cron Jobs, you can schedule tasks to run at precise times. The syntax looks like this:
Here’s what each part does:
-
-
/usr/local/bin/phptells the server to run the PHP executable. -
/home/username/sitename/wp-cron.phpis the path to the site’s cron script. -
/usr/bin/flock -n /tmp/sitename.lockprevents overlap; if one cron run is still running when the next arrives, the second one will skip instead of stacking. -
> /dev/null 2>&1discards output so your email inbox isn’t flooded with logs.
-
Staggering is Critical
The most important part of this process is staggering. Never set all your sites to run at the same minute. Spread them out so the server isn’t trying to run eight cron jobs at once. Here’s an example with three fictional websites:
1. greengrasslawns.com (main domain)
Runs every 15 minutes at : 01, :16, :31, :46
Command: /usr/bin/flock -n /tmp/greengrass.lock /usr/local/bin/php -q /home/example/public_html/wp-cron.php > /dev/null 2>&1
2. petcareplus.net (addon domain)
Runs every 30 minutes at :04 and :34
Command: /usr/bin/flock -n /tmp/petcare.lock /usr/local/bin/php -q /home/example/petcareplus.net/wp-cron.php > /dev/null 2>&1
3. citynewsdaily.org (addon domain)
Runs every 30 minutes at :07 and :37
Command: /usr/bin/flock -n /tmp/citynews.lock /usr/local/bin/php -q /home/example/citynewsdaily.org/wp-cron.php > /dev/null 2>&1
In this setup, the first site fires four times an hour, the second site fires twice an hour, and the third site fires twice an hour — but none of them overlap. The load is spread out, and the server never faces the midnight pile-up that used to cause outages.
How to Test if It’s Working
Testing is easy. The simplest method is to create a new post and schedule it for five minutes in the future. If the post publishes shortly after the next cron window, your system cron is firing correctly. For a more detailed view, a developer can temporarily add a diagnostic file that lists scheduled events and their timestamps, but for most users, the scheduled post test is all that’s required. Here is a simple file to upload to each folder at the domain/root level of each domain. Name it croncheck.php and then call it — meaning enter the following into the browser — as sitename.com/croncheck.php
<?php
// /croncheck.php (temporary)
define(‘WP_USE_THEMES’, false);
require __DIR__ . ‘/wp-load.php’;
header(‘Content-Type: text/plain; charset=utf-8’);
if ( ! function_exists(‘_get_cron_array’) ) {
require_once ABSPATH . ‘wp-includes/cron.php’;
}
// Current times
$serverNow = new DateTime(‘now’); // server tz
$siteTz = wp_timezone(); // WP site tz from Settings → General
$siteNow = new DateTime(‘now’, $siteTz);
echo “Server time: ” . $serverNow->format(‘Y-m-d H:i:s T’) . “\n”;
echo “Site time: ” . $siteNow->format(‘Y-m-d H:i:s T’) . “\n\n”;
$crons = _get_cron_array();
if ( empty($crons) || !is_array($crons) ) {
echo “No scheduled events found.\n”;
exit;
}
// Flatten & sort events
$events = [];
foreach ($crons as $ts => $hooks) {
foreach ($hooks as $hook => $list) {
$events[] = [‘ts’ => (int)$ts, ‘hook’ => $hook];
}
}
usort($events, fn($a,$b) => $a[‘ts’] <=> $b[‘ts’]);
echo “Scheduled events (Site local time) — soonest first:\n”;
foreach ($events as $e) {
$when = (new DateTime(‘@’ . $e[‘ts’]))->setTimezone($siteTz);
$diff = $siteNow->diff($when);
$ahead = $when > $siteNow;
$mins = ($diff->days*24*60) + ($diff->h*60) + $diff->i;
$label = $ahead ? “in ~{$mins}m” : “~{$mins}m ago”;
echo $when->format(‘Y-m-d H:i:s T’) . ” => {$e[‘hook’]} ({$label})\n”;
}
echo “\nNote:\n”;
echo “- System cron every 15/30 min only TRIGGERS WP-Cron; WP-Cron runs tasks that are DUE.\n”;
echo “- Daily/weekly hooks will naturally show hours/days ahead. That’s expected.\n”;
echo “- To prove triggers, schedule a post a few minutes ahead or create a short-interval test event.\n”;
When you call it, check the closest execution time coming up. After that time reload and if it has a new time, voila! Granted, tons of ways to check, but this keeps it simple for most folks.
Why Shared Hosting Is Sensitive
Shared hosting is affordable, but it’s also unforgiving. Providers run hundreds of accounts on the same server, each with its own process and memory limits. One WordPress install with poorly configured cron jobs won’t bring down the entire machine — but ten installs all running their nightly jobs at once will trigger the account’s resource limits. That’s when sites vanish for five minutes at a time. On a VPS or dedicated server, you might have enough headroom to absorb that burst. On shared hosting, you don’t. That’s why moving to real cron and staggering jobs isn’t just best practice. It’s essential.
The Bottom Line
By disabling WordPress’s pseudo-cron, adding system cron jobs, staggering their execution times, and applying flock for safety, you eliminate the nightly blackout and restore stability. Tasks still run, plugins still clean up, updates still check in — but they do so on your terms, in a controlled flow. For anyone running multiple WordPress sites on shared hosting, this simple adjustment is the difference between nightly downtime and smooth, uninterrupted service.




