The other day I received a notification from my host that my site was requiring more CPU time than was allotted with the hosting plan. I’m allowed 10,000 CPU seconds within any given 24 hour period and 2,000 CPU seconds within any given 2 hour period.
Exceeding either results in a warning to start with, and presumably some form of lock down action if it were to continue without resolution. When I logged into cpanel to check my stats, er, Time to Loot was roughly double in each of those limits.
The only change I’d made recently was to the theme — I shifted from an Elementor driven setup to a Divi one. Specifically, a cut back version of the Extra theme (which has the Divi builder built into it). I worried that now that I’d customised Extra to my liking I might have to shift back if it was driving the extra script executions.
I kinda knew also that I’d been driving a fair number of scripts on the site (the most visible of which being the WP RSS Aggregator plugin which drives my blogroll). But nothing there had changed recently.
I was contemplating simply biting the bullet and upgrading to the next plan, but something about it all still seemed off. The daily traffic hadn’t even been that high. I mean it was good — but nothing out of the usual. So I struck up conversation with support and unlike how most stories that start this way go — I ended up with a good outcome.
Yeah – so, turns out… Time to Loot was under threat from foreign hostile forces! It was under attack! :O
I was already running a WP Security plugin fortunately. Incidentally one that I thought might’ve been contributing toward the high CPU usage, but it had been in place for several months already. So they couldn’t really get to or do anything.
But here’s an interesting fact: WordPress has its own mechanism for launching scheduled tasks. It has built its own version of Cron in the form of the WP-Cron.php file. Every time this script is run, it scans your site for scheduled tasks that should have been done or kicked off at around the time of its execution.
But unlike a proper Cron job which executes fully from the server side — including the scheduling — wp-cron.php requires a different trigger to kick it off.
The folk at WordPress decided the best trigger was… Er… The loading of any other post, page or piece of content on your blog. Anything. Every time. Refresh a post 50 times, it will trigger wp-cron.php 50 times. Expand that out to 15,000 times and it starts to become a bit of a problem.
There was a bot crawling the site, pulling everything from it. It had consumed 2.2GB of data in a relatively short period and each page or post it hit triggered a check for other scripts to execute. I don’t even have 15,000 pieces of content here so who knows what it was doing.
In any case — like I alluded to; support was exceptional. They looked into the problem then and there over live chat and identified the problem pretty much immediately then gave me three fixes, the first of which they implemented on the spot on my behalf.
Solution 1: Strengthen the .htaccess file
While I had the WP Cerber Security plugin installed, preventing anything majorly bad from happening — this was still only preventing access after the request to server was already made.
The support agent I was talking to was able to implement the ‘6G Firewall‘ ruleset into my .htaccess file to prevent bad bots from even being able to get a valid response to a request against my domain.
It blocks a wide range of bad and unknown bot types, while not impacting search engine crawlers.
As long as your host is running Apache 2+ and has htaccess files enabled, you can drop this code in and with no further config enjoy much better security.
That alone probably would have solved the immediate issue, but this was just the first solution suggested.
Solution 2: Install ‘Wordfence Security’
It’s a plugin which at surface seems very similar in nature to the WP Cerber Security plugin I was already using.
But after installing and digging a little deeper, it does go at least a bit further it seems. The adjustments made to the .htaccess file are fairly static in nature. They’re wild carded to pickup a wide range of things but beyond that will be unchanging.
Wordfence even in its free version will dynamically learn from the traffic patterns hitting your site and can (with the right permissions granted) adjust your .htaccess on the fly to block new attacks.
Solution 3: Reduce frequency of WordPress ‘Heartbeats’
So here’s something else I didn’t know WordPress did.
If you’re logged in to the Admin part of the site, e.g., the dashboard, editing a post, etc — WordPress updates your status every 15 seconds. This was identified as a potential tertiary problem in my case, as there had been a high number of admin-ajax.php executions as well. (Which in retrospect actually made sense, since I had a ‘new post’ page open for most of the day.)
The intent here seems to be mostly for multi-user blogs so that users can easy when other users are editing posts or pages. For a single user blog this could pretty much be turned off entirely to no ill effect.
I went with a bit of a compromise position though and set the heartbeat time to occur once every 60 seconds instead.
You can edit this into the WP config directly, but easier by far just to use a plugin.
Solution 4: Disabling WP-Cron and switching to ‘real’ Cron instead.
This one actually sits somewhere between security and efficiency, really. By disabling wp-cron.php; you remove this vector of attack against your server’s performance.
To disable WordPress from running wp-cron.php every time a page is loaded, you need to head into and edit your wp-config.php file in your main WordPress install folder. In there, scroll down until you find: ” /* That’s all, stop editing! Happy blogging. */ “
Right before that line, enter: define(‘DISABLE_WP_CRON’, true);
How you tell your server to actually execute the script will differ a bit from host to host. Fortunately on SiteGround (my host), their cpanel instance also includes a GUI for scheduling cron tasks.
But what you want to achieve is to essentially run the following command every 30 minutes:
wget -q -O – http://yourdomain.com/wp-cron.php?doing_wp_cron >/dev/null 2>&1
Doing it this way has a few other benefits too. e.g., Ever scheduled a post and had it completely fail to actually… You know… Post?
That’s because if you don’t have any visitors or site hits shortly after the time the post is scheduled to go — it just won’t. Running your wp-cron via ‘real cron’ on a reliable schedule essentially ensures this won’t happen any more.
…Back within the safe band of CPU seconds for both 2h and 24h measures again, so I don’t have to upgrade my plan. Thank God. I wasn’t looking forward to finding out what that price uplift would’ve been, but I would imagine it to be not insignificant.
In any case, huge thanks to the SiteGround support for being on top of this and helping me solve it. (Including giving a temporary reprieve on the limits while we were working through the issue.)
May not be the cheapest host provider out there, but this would be the third or forth time I’ve had to call on their support so far — and every time it has been superb. Can highly recommend if you’re in the market for a host.
If not, hopefully some of the other information in here might be useful to you!