Skip to content
Frano Čokljat

Own project · kompas.hr

Kompas: recovering my own travel site from a spam hack

My own travel site, kompas.hr, was hit by a plugin hack that put ~153,000 spam URLs in Google. A static rebuild cut the index to 44 pages; traffic has partly recovered.

Owner
Me. Bought in 2019; this is not client work
Site
kompas.hr, a travel guide to Dubrovnik, Cavtat and Croatia
Monetisation
Display ads and affiliate links
Stack
WordPress → static Astro on Cloudflare
Published
, updated
  • ~153k → 44

    URLs in Google's index, at the peak in autumn 2025 and now

  • ~74k → <15k

    URLs known to Google, July to October 2026

  • ~⅓

    of pre-incident clicks at the summer 2026 peak, easing since August

This is my own site

kompas.hr is a site I own and run. It is not a client engagement. I bought the domain, built the site, earn from it, and made every decision in this story, including the mistake that let the hack in. That is also why I can show the full Search Console data here, unredacted.

Background

Kompas was a well-known Croatian travel agency. After the company went bankrupt, the kompas.hr domain expired, and I bought it in 2019. The name and the links pointing at it carried existing authority. I used that to build a new site on the domain: a travel guide to Dubrovnik, Cavtat and Croatia, written for people planning a trip there.

It is a commercial site. It earns from display ads (Journey by Mediavine) and from affiliate links to tours and car hire (GetYourGuide and DiscoverCars). Over six years it grew to 400–550 organic clicks and 15,000–18,000 impressions a day.

It ran on WordPress, with a page builder and plugins I had added over the years and didn’t review often enough.

The incident

In September 2025 one of those plugins, an obscure one I wasn’t maintaining, was compromised. Through it, a backdoor generated spam pages on the fly: the homepage with a numeric query parameter, like /?k=747233416, each serving spam. It could also serve fake XML sitemaps to push those URLs into Google faster, and it worked. At the peak Google had about 153,000 URLs from the domain in its index. The real content would have fit in a single sitemap.

Search Console is how I found out: it emailed me about a sudden rise in indexed pages. Within about a week, around the start of October, organic clicks fell from several hundred a day to near zero.

There was no manual action. The Manual actions report stayed empty, so the drop was algorithmic. Recovery would depend on Google recrawling and re-evaluating the site, not on a reconsideration request.

Search Console performance chart for kompas.hr, daily clicks and impressions from June 2025 to October 2026: 77,100 clicks and 3.53 million impressions in total. Clicks run at 400 to 550 a day and impressions at 15,000 to 18,000 until late September 2025, then both fall to near zero within about a week. They stay low through the winter, climb from spring 2026 and jump in late May to about 150 to 200 clicks and 8,000 to 9,000 impressions a day, then ease from August to about 90 clicks and 4,000 impressions by late September.
Google Search Console, Performance, 16 months to October 2026 · Open full size

Cleaning it in place didn’t work. I deleted the malicious folder several times and it kept coming back. The payload sat in wp-content/mu-plugins/. Must-use plugins run on every page load and never appear in the admin plugin list, which makes that folder a good hiding place. The file was obfuscated PHP that fetched spam from an external server. It regenerated because the compromised plugin kept writing it back. An .htaccess rule to answer 410 for the spam pattern didn’t hold either: the requests still reached WordPress, which served the homepage.

The rebuild

I chose to rebuild instead of cleaning. A proper clean meant replacing core files, reinstalling every plugin and the page builder, scrubbing the database, and hoping nothing had been missed. Rewriting the articles I wanted to keep in Markdown was less work than that, and it removed the problem instead of patching it.

The new site is built with Astro and served as static files from Cloudflare (Workers with static assets), deployed from GitHub. The static output is the reason. There is no PHP, no plugin and no database on the site any more, so nothing exists for this kind of injection to write to or run from. The only server-side code is a small Worker that handles redirects and the contact form.

I started the rebuild in mid-October 2025, and used it to retire a large part of the legacy archive instead of restoring it. The site now keeps 34 articles, organised into Dubrovnik, Cavtat and Croatia guides.

The index cleanup

Two kinds of URL had to leave the index, and they needed opposite treatment. The spam had to disappear. The retired articles were real pages with links and history worth keeping.

Spam is shut off before it reaches the site. The Worker was built to answer 410 Gone for any request with a numeric k parameter: a 410 tells Google a page was removed deliberately and permanently. Today those requests don’t get that far. A Cloudflare firewall rule blocks any request with a k parameter with a 403, before the Worker runs, so spam traffic never costs a Worker request. I also put the ?k= pattern through the Search Console removals tool, so the spam left the results while recrawling caught up.

Old URLs keep their equity where there is somewhere to send it. Every old WordPress URL with an equivalent on the new site redirects there with a 301: 38 in all, such as /dubrovnik-city-walls/ to /dubrovnik/attractions/city-walls. Retired articles with no equivalent return 404 and drop out.

The sitemap lists only what exists. It is generated from the build, so it contains the kept pages and nothing else. I submitted it fresh, which gave Google one clean statement of what the site is, separate from everything it was being told to forget.

Result

  • The index matches the site. About 153,000 URLs were indexed at the peak in autumn 2025. When the page indexing chart below starts, in late July 2026, Google knew about 74,000. It now knows under 15,000: 13,400 correctly excluded and 44 indexed.
  • Traffic came back, but only partly. Clicks started climbing in spring 2026 and jumped in late May. At the peak, in June and July, the site had about a third of its pre-incident clicks and about half its impressions. Since August both have eased: by late September it was getting 80–100 clicks and about 4,000 impressions a day, under a fifth of the clicks it had before.
  • The way in is closed. A static build with no server-side plugins can’t be injected into the way the WordPress site was.
Search Console page indexing chart for kompas.hr. Known pages hold at about 74,000 from late July 2026, step down through late August and early September, and settle at about 14,000 by late September: 13,400 not indexed and 44 indexed.
Google Search Console, Page indexing, July to October 2026 · Open full size

The point

Traffic came back from near zero while the index shrank by more than 99%. Fewer, better pages, on a static build that can’t be injected into.

It hasn’t come back to where it was. Some of the slide since August is seasonal: in 2025, before the hack, clicks also fell from June to September as the Dubrovnik season wound down. The rest I can’t yet separate from the 13,400 dead URLs Google is still processing, and from how a domain is judged after months of serving spam. The technical foundation is clean now, so the next work is content.

What I’d do differently

Audit plugins on a schedule, and delete what I don’t maintain. The way in was an obscure plugin I had stopped paying attention to. Fewer plugins, each with an active maintainer, and anything unused deleted rather than just deactivated.

Watch the page count myself. Search Console’s email did catch it, but by the time Google reports a jump in indexed pages, the pages are already indexed. A daily check of my own, comparing known URLs against the sitemap or flagging unfamiliar URL patterns in the logs, would have caught it sooner.

Find the entry point before deleting anything. If deleted malware keeps coming back, the visible file isn’t the source. Auditing every installed plugin first, starting with mu-plugins, would have saved the rounds of cleanup that didn’t stick.

Track the two removals separately from day one. The spam cleanup and the archive retirement ran at the same time, so it is hard to say how much of the recovery came from each. Tagging the two URL sets separately in reporting from the start would have answered that.