I was reviewing my analytics last night and saw thousands of visitors, admin was timing out, and my server’s CPU was spiking. After already battling and thwarting attacks last quarter, I had a head start on troubleshooting and found the culprit: WordPress’ internal search.
Side note: Martech Zone’s growth really seems to be annoying someone! I’m not sure what I did to deserve this kind of attention, but yeesh…
Attackers and aggressive bots often exploit the default WordPress search mechanism to launch a Resource Exhaustion Attack. This is a type of Application-Layer Denial of Service (DoS) attack designed not to steal data, but to overwhelm your database server until it gives up.
This article explains why this happens, how to confirm you are under attack using MySQL tools, and how to implement a two-layer code defense to stop it.
Table of ContentsThe Anatomy of the AttackThe Multiplier EffectDiagnosis: How to Know You’re Under AttackMethod 1: SHOW PROCESSLIST (Immediate Diagnosis)Method 2: The Slow Query Log (Historical Diagnosis)The Solution: A Double-Layer DefenseImplementationHow to Test It
The Anatomy of the Attack
To understand the defense, you must understand the weakness.
By default, WordPress performs searches using MySQL LIKE queries with wildcards at both ends (e.g., LIKE ‘%term%’). In database terms, a leading wildcard forces MySQL to perform a Full Table Scan. It cannot use standard indexes efficiently; it must literally read every single row in your wp_posts table from the disk to check if the term exists somewhere inside the content.
The Multiplier Effect
The attack works by sending massive search queries containing dozens or hundreds of words.
If a bot searches for 50 random words, WordPress translates that into a single SQL query containing 50 separate AND … LIKE ‘%word%’ clauses. MySQL now has to perform complex cross-referencing across thousands of rows for 50 different terms simultaneously.
A few dozen of these requests per second can easily cripple a standard MySQL server.
Diagnosis: How to Know You’re Under Attack
If your site is hanging, you need to look at what your database is currently doing.
Method 1: SHOW PROCESSLIST (Immediate Diagnosis)
If you have SSH access to your server or access to phpMyAdmin, run the following SQL command:
SHOW FULL PROCESSLIST;
Look at the results. If you are under a search attack, you will see numerous processes in a “Sending data” or “Locked” state running very long queries on wp_posts that look like this:
SELECT SQL_CALC_FOUND_ROWS wp_posts.ID FROM wp_posts WHERE 1=1 AND (
(wp_posts.post_title LIKE ‘%word1%’) OR (wp_posts.post_content LIKE ‘%word1%’)
) AND (
(wp_posts.post_title LIKE ‘%word2%’) OR (wp_posts.post_content LIKE ‘%word2%’)
) AND … [repeating for 50+ words] …
ORDER BY wp_posts.post_date DESC LIMIT 0, 10
If you see stacks of these queries taking several seconds to run, you are experiencing a Resource Exhaustion Attack.
Method 2: The Slow Query Log (Historical Diagnosis)
If your host enables the MySQL slow query log, check it. You will find entries showing queries similar to the one above taking an unusual amount of time (e.g., Query_time: 5.2345) to execute.
The Solution: A Double-Layer Defense
We cannot rely on standard plugins to fix this, as the attack happens before most plugins load. We need to intercept the search request at the earliest possible point in WordPress core execution: the pre_get_posts hook.
We will implement a two-layer defense strategy:
The Bouncer (Hard Character Limit): If a query is suspiciously long (e.g., over 200 characters), we assume it’s malicious and block it immediately with a 403 Forbidden error. This saves PHP and MySQL from doing any heavy lifting.
The Filter (Keyword Optimization): For legitimate but wordy searches, we strip out common “stop words” (like “the”, “a”, “is”) and then truncate the remaining query to a safe number of high-value keywords (e.g., the first 4).
Implementation
Add the following code to your active theme’s functions.php file, or preferably, into a site-specific plugin.
is_main_query() || ! $query->is_search() ) {
return;
}
$max_characters = 200;
$max_keywords = 4;
$search_string = $query->get( ‘s’ );
if ( empty( $search_string ) ) {
return;
}
// Layer 1: Character Limit
if ( strlen( $search_string ) > $max_characters ) {
wp_die( ‘Search query too long.’, ‘Search Blocked’, array( ‘response’ => 403 ) );
}
// Layer 2: Keyword Optimizer
$stop_words = wp_get_search_stopwords();
// UPDATED REGEX: Added \+ to handle plus signs in URLs
$all_words = preg_split( ‘/[\s,\+]+/’, $search_string, -1, PREG_SPLIT_NO_EMPTY );
$meaningful_words = array_filter( $all_words, function( $word ) use ( $stop_words ) {
return ! in_array( strtolower( $word ), $stop_words );
});
if ( count( $meaningful_words ) > $max_keywords ) {
$truncated_array = array_slice( $meaningful_words, 0, $max_keywords );
$query->set( ‘s’, implode( ‘ ‘, $truncated_array ) );
}
}
?>
How to Test It
Test Layer 1: Try to search for a massive block of text (over 200 characters). You should immediately see a standard WordPress error page saying “Search query too long.”
Test Layer 2: Search for a long, wordy phrase like: “I am looking for the best italian pizza recipes in downtown chicago”.
Check the search results page title. You should see that WordPress actually searched for something much shorter, likely just the key terms like: “italian pizza recipes chicago”.
By implementing this code, you ensure that no matter what nonsense a user or bot pastes into your search bar, your MySQL database only ever receives a lean, manageable query.
©2026 DK New Media, LLC, All rights reserved | DisclosureOriginally Published on Martech Zone: How to Prevent Resource Exhaustion Attacks on WordPress Search (MySQL DoS)