The cheapest way to secure a brochure site is to stop running an application on it
Two of the sites on a compromised server were brochure sites. Six or seven pages of copy, an address, a contact form, some images. Neither had been meaningfully edited in over a year. Both were running a full content management system with a database behind it, a dozen extensions, an administrator login, and a scheduled task runner that fired on every request.
Both had been thoroughly compromised, and among other things had been used to inject thousands of spam URLs that were then indexed by search engines.
We did not clean them and re-harden them. We rebuilt each one as a single static HTML file and deleted the application entirely.
What the application was actually providing
This is the question worth asking before any hardening exercise, and it is a business question, not a technical one: what does this software do for the people who own it?
The honest answer for both sites was: it lets someone edit the text without a developer. That is a real benefit and I am not dismissing it. But it had been exercised roughly never in the preceding year, and the price for keeping the option open was a dynamic application, an interpreter, a database, a login form, an extension ecosystem, and a patch cadence that nobody was keeping up with.
Set that out plainly and the trade is obvious. The site was paying, continuously, for a capability it used once a year. And “paying” here does not mean hosting cost — it means carrying an administrative login exposed to the internet, and a dozen third-party code dependencies whose security is somebody else’s release schedule.
I would have made the same call on the day the sites were first built, if anyone had asked the question then. Nobody did, because a content management system is what you reach for by default when someone says “we need a website”, regardless of whether the site has content that changes.
What the rebuild removed
The rebuilt sites are one HTML file each, served by the web server as a file. There is no interpreter in the request path, no database, no login, no scheduled tasks, no extensions.
Enumerate the classes of vulnerability that simply no longer apply:
- No SQL injection, because there is no query and no database.
- No authentication bypass or credential stuffing, because there is no authentication.
- No vulnerable third-party extension, because there is no plugin architecture.
- No file upload vector, because nothing accepts uploads.
- No remote code execution through the interpreter, because the interpreter is not invoked.
- No configuration file containing database credentials, because there are no credentials.
That is not a reduced attack surface. For the great majority of what actually gets exploited on these sites, it is an absent one. The vhost is configured to return a hard 404 for any request ending in the interpreter’s file extension, so even a stray file left behind cannot be executed.
The deployment process is copying one file to the server and clearing the CDN cache for that domain. That is the whole procedure. There is no staging database to keep in sync, no migration to run, no version compatibility matrix between the core application and its extensions.
The part that needed care: staying deleted
Here is the detail that is easy to get wrong, and it is the reason this is worth writing up rather than just doing.
Those sites had thousands of spam URLs injected and indexed. Search engines had crawled them, stored them, and would keep requesting them for a long time after the compromise ended. Crawlers are persistent about paths they have seen before.
The instinct when rebuilding is to be helpful: catch unmatched paths and redirect them to the home page, so nobody ever sees an error. That instinct is wrong here, and expensively so. A redirect — or worse, a 200 response with the home page content — tells the crawler the URL still resolves. Deindexing stalls. The spam paths stay in the index, associated with the domain, for as long as the server keeps answering them agreeably.
A genuine 404 is the signal that removes them. It says the resource does not exist, and repeated 404s are what eventually causes a crawler to drop a URL from its index.
So the rule for these vhosts is explicit and written down: unknown paths must keep returning a real 404. Not a redirect, not a soft error page served with a success status, not a catch-all rewrite. This is the single most likely thing for a future maintainer to break, because breaking it looks like an improvement — “we should not show users an ugly error page” is a reasonable-sounding sentence that would quietly undo the cleanup.
The general form: HTTP status codes are an interface to machines, and choosing them for the comfort of humans breaks that interface. You can serve a friendly, designed, branded page and still return 404 with it. The two decisions are independent, and conflating them is one of the more common ways sites damage their own search presence without ever noticing.
When this is the wrong answer
I am not arguing that static is always right. It is right when the content genuinely does not change often, and wrong the moment somebody needs to publish on a Tuesday without waiting for a developer.
The test I would apply is a frequency question, answered honestly with evidence rather than aspiration: look at the actual edit history for the last twelve months. Not what people say they will do — what they did. If the answer is a handful of edits, the operational cost of a developer making them is lower than the cost of maintaining an application. If the answer is weekly, you need the application, and you should invest properly in keeping it patched and isolated instead.
The intermediate options are worth knowing about too. A static site generator with content in version control gives non-developers a review workflow and gives you a build step, without an interpreter in the request path. A managed content service with a static front end separates the editing surface from the public one, so a compromise of the editor does not put arbitrary code on your domain. Both are more work than one HTML file and less work than a self-hosted application.
The principle
Before hardening a system, ask what it is for, and be prepared for the answer to be “less than we have built”.
The most reliable way to eliminate a class of vulnerability is to remove the capability that creates it. Not to configure it more carefully, not to add a scanner, not to buy a firewall in front of it — to establish that nobody actually needs it, and then delete it.
Every capability you keep is one you have committed to patching for as long as the system exists. That commitment is easy to make and hard to honour, and the sites that get compromised are always the ones where it was made without anyone realising they were making it.