DEV Community

MD Pabel
MD Pabel

Posted on

How I Used 410 Gone Rules to Remove WordPress SEO Spam URLs

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 .html pages
  • 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
Enter fullscreen mode Exit fullscreen mode

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:

  1. Start PHP
  2. Load WordPress core
  3. Load active plugins
  4. Connect to MySQL
  5. Run database queries
  6. Load the theme
  7. 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
Enter fullscreen mode Exit fullscreen mode

Hacked directories

/pages/fake-product.html
/jp/random-category/
/products/23932
Enter fullscreen mode Exit fullscreen mode

Spam words inside paths

/online-casino-offer/
/poker-jackpot/
/viagra-product.html
Enter fullscreen mode Exit fullscreen mode

Generated HTML pages

/random-folder/product-name.html
/pages/discount-item.html
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

That can be a legitimate post URL.

During the incident, some unwanted URLs also used the same structure:

/?p=23981
/?p=23932
/?p=23919
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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]
Enter fullscreen mode Exit fullscreen mode

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/"
Enter fullscreen mode Exit fullscreen mode

The expected response was:

HTTP/2 410
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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:

  1. Remove spam URLs from the legitimate sitemap
  2. Allow Googlebot to request them
  3. Return 410 for confirmed malicious patterns
  4. 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/
Enter fullscreen mode Exit fullscreen mode

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:

  1. Clean the infection
  2. Collect URL samples
  3. Group them by structure
  4. Confirm that the patterns are not legitimate
  5. Return 410 before WordPress loads
  6. Temporarily suppress urgent results
  7. 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)