A hacked WordPress website can be cleaned in a few hours, but the damage left in Google may remain much longer.
In one cleanup, the website looked normal when opened directly. Google Search showed something completely different:
- Japanese product pages
- Gambling-related URLs
- Random numerical query parameters
- Fake
.htmlpages - Content the website owner had never published
Approximately 242,000 unwanted URL variations were associated with the incident.
Removing the malware stopped new pages from being generated, but Google already knew about many of the hacked URLs.
That created a second problem:
How do you permanently retire thousands of generated URLs without loading WordPress for every request?
My solution was to identify repeatable URL patterns and return 410 Gone at the Apache level.
I documented the complete malware and Google index recovery in this WordPress SEO spam cleanup case study. This article focuses specifically on the server-side implementation.
Why malware removal was not enough
Suppose malware creates URLs like these:
https://example.com/?a=839472938
https://example.com/?q=739284729
https://example.com/pages/fake-product.html
https://example.com/jp/random-product/
https://example.com/products/23932
After the malicious PHP is removed, those URLs may stop displaying spam.
But Googlebot, scrapers, security scanners, and other bots may continue requesting them for months.
Without a server-level rule, every request might still:
- Start PHP
- Load WordPress core
- Load active plugins
- Connect to MySQL
- Run database queries
- Load the theme
- Generate a full 404 page
That is unnecessary work for URLs that should never exist again.
Why I chose 410 Gone
A 404 Not Found response means the server cannot find the requested resource.
A 410 Gone response means the resource was deliberately removed and is not expected to return.
Both statuses can eventually remove a URL from Google. I used 410 because these URLs were confirmed hacked resources that had no legitimate replacement.
The important requirements were:
- The URL must not return HTTP 200
- The response must not be a soft 404
- Googlebot must be able to crawl the URL
- The status must remain consistent
- Legitimate pages must not match the rule
A 410 response is not an instant deindexing button. Google still needs to revisit and process the URL.
Identify patterns before writing rules
I did not write firewall rules based on one example URL.
I collected samples from:
- Google Search Console
- Google
site:searches - Search Analytics API exports
- Server access logs
- Existing malicious sitemap files
The samples revealed several repeated structures.
Random query parameters
/?a=839472938
/?q=739284729
/?x=193847293
Hacked directories
/pages/fake-product.html
/jp/random-category/
/products/23932
Spam words inside paths
/online-casino-offer/
/poker-jackpot/
/viagra-product.html
Generated HTML pages
/random-folder/product-name.html
/pages/discount-item.html
The rules were based only on patterns confirmed on that particular website.
A case-specific Apache firewall
The following is a simplified example.
Do not copy it unchanged. Back up the existing .htaccess file and verify that every pattern is malicious on your own site.
Place the rules before the normal WordPress rewrite block.
# ----------------------------------------------------------------------
# CASE-SPECIFIC WORDPRESS SEO SPAM FIREWALL
# ----------------------------------------------------------------------
ErrorDocument 410 "<h1>410 Gone</h1><p>This hacked spam URL has been permanently removed.</p>"
<IfModule mod_rewrite.c>
RewriteEngine On
# --------------------------------------------------
# 1. CONFIRMED RANDOM QUERY PARAMETERS
# --------------------------------------------------
# Examples:
# ?a=12345678
# ?q=987654321
RewriteCond %{QUERY_STRING} (^|&)(?:a|q)=[0-9]{5,}(&|$) [NC]
RewriteRule ^ - [G,L]
# --------------------------------------------------
# 2. CONFIRMED MALICIOUS DIRECTORIES
# --------------------------------------------------
RewriteRule ^(?:pages|jp)(?:/|$) - [G,L,NC]
RewriteRule ^products/[0-9]+(?:/|$) - [G,L,NC]
# --------------------------------------------------
# 3. CONFIRMED SPAM WORDS IN THE PATH
# --------------------------------------------------
RewriteRule ^.*(?:casino|gambling|viagra|cialis|poker|baccarat|roulette|jackpot).*$ - [G,L,NC]
# --------------------------------------------------
# 4. OPTIONAL HTML RULE
# --------------------------------------------------
# Enable only when the legitimate website has no real .html URLs.
# RewriteRule ^.*\.html$ - [G,L,NC]
</IfModule>
Apache’s G flag returns HTTP 410 Gone.
The L flag stops further rewrite processing for the matching request.
Be extremely careful with WordPress ?p= URLs
WordPress uses query URLs like this:
https://example.com/?p=123
That can be a legitimate post URL.
During the incident, some unwanted URLs also used the same structure:
/?p=23981
/?p=23932
/?p=23919
Blocking every ?p= request would have been dangerous.
Before creating a rule, I checked whether the suspicious ID range contained real WordPress content:
SELECT ID, post_title, post_status
FROM wp_posts
WHERE ID BETWEEN 23000 AND 24999
ORDER BY ID ASC;
Only after confirming the relevant range did not contain legitimate content could a narrow range rule be considered.
For example:
# Example only: matches p values from 23000 through 24999.
RewriteCond %{QUERY_STRING} (^|&)p=2[3-4][0-9]{3}(&|$) [NC]
RewriteRule ^ - [G,L]
A safer rule is always better than a broad one.
Test the rules with curl
I tested known spam URLs:
curl -I "https://example.com/?a=123456789"
curl -I "https://example.com/?q=987654321"
curl -I "https://example.com/pages/fake-product.html"
curl -I "https://example.com/jp/fake-category/"
The expected response was:
HTTP/2 410
I then tested legitimate areas:
curl -I "https://example.com/"
curl -I "https://example.com/wp-login.php"
curl -I "https://example.com/wp-admin/"
curl -I "https://example.com/?s=wordpress"
curl -I "https://example.com/?p=123"
Those URLs needed to continue returning their normal status or redirect.
Never assume a rewrite rule is safe because the syntax is valid.
How this reduced server usage
The firewall stopped matching requests before WordPress handled them.
That meant the server did not need to fully load:
- WordPress core
- Plugins
- Theme files
- Database queries
- A styled 404 template
The benefit became noticeable because bots continued requesting old hacked URLs after the infection had been removed.
For a small rule set, .htaccess can be practical.
For a very large or high-traffic site, equivalent rules may be better placed in:
- Apache virtual-host configuration
- Nginx configuration
- Cloudflare
- A web application firewall
- A custom application-level route handler
Do not block the URLs in robots.txt
Google must request a hacked URL to observe the 404 or 410 response.
If the URL is blocked in robots.txt, Googlebot may be unable to see that it has been permanently removed.
My approach was:
- Remove spam URLs from the legitimate sitemap
- Allow Googlebot to request them
- Return 410 for confirmed malicious patterns
- Monitor recrawling through logs and Search Console
Search Console removal is only temporary
I also used Google Search Console’s Removals tool for urgent suppression.
For dedicated hacked directories, the option Remove all URLs with this prefix was useful:
https://example.com/pages/
https://example.com/jp/
https://example.com/products/
However, Search Console removal is not the permanent fix.
The permanent fix was the combination of:
- Malware removal
- Consistent 410 responses
- A clean sitemap
- Continued monitoring
Final lesson
The site did not really have 242,000 separately created WordPress pages.
It had a smaller number of malicious patterns generating thousands of URL variations.
Once those patterns were identified, the recovery became manageable:
- Clean the infection
- Collect URL samples
- Group them by structure
- Confirm that the patterns are not legitimate
- Return 410 before WordPress loads
- Temporarily suppress urgent results
- Monitor Googlebot and server logs
The full investigation, Search Console workflow, sitemap experiment, and malware-cleanup process are documented in my complete spam URL removal case study.
Top comments (0)