DeenLytics

Ingegneria

Why we moved our own website off WordPress

Our old site ran on a purchased theme and was attacked repeatedly. What we replaced it with, and the two decisions where our first plan was wrong.

Di Injamam Ahmed5 min di lettura

Our own website ran on WordPress with a purchased theme. It was attacked repeatedly.

Not because WordPress is bad software — it runs a large share of the web and does it well. Because a marketing site built the usual way is a strange shape: a database, an admin login, PHP execution and a stack of plugins, all exposed to the internet, in service of something whose entire job is to show a dozen pages that change once a month.

We rebuilt it. This post is what we replaced it with, and — more usefully — the two decisions where our first plan turned out to be wrong.

What the old site actually exposed

It is worth being precise about the attack surface, because "WordPress got hacked" is a lazy summary that teaches nobody anything.

A themed WordPress install gives an attacker several independent ways in. There is /wp-login.php, which is a public username-and-password form that anyone can hammer forever. There are plugins, each one code from a stranger running with full access to the database and the filesystem, and each one an announcement the day a CVE lands. There is PHP execution in the upload directory if any of that goes wrong. And there is the theme itself — a purchased theme is a large pile of code you did not write, do not read, and usually cannot patch without losing your changes.

None of that is required to display our services and some case studies. We were carrying the risk of a web application to run a brochure.

The rebuild

The new site is Next.js. Pages are generated at build time and served as files. Content — services, projects, the case studies, this post — lives in typed files in the repository, which means a wrong field name is a build error rather than something you find on the live site.

That removes the categories of problem above outright. There is no /wp-login.php to brute force, no plugin to patch, no PHP to execute, and no theme underneath doing things we cannot see.

On top of that:

  • Strict security headers and a content security policy on every response.
  • Three languages from one codebase, including Arabic right-to-left, with proper hreflang and self-referencing canonicals.
  • Every old URL redirects rather than 404s. We checked this against the live sitemap of the old site rather than assuming.
  • Lighthouse 100 across performance, accessibility, best practices and SEO on desktop, and 96 for performance on throttled mobile.

The first thing we got wrong: no database

The plan said no database. Static files, no moving parts, nothing to compromise. It was the right instinct and it lasted until we tried to use the site.

Three things broke it. Publishing a blog post should not require a developer, a git commit and a deploy. Enquiries need to be recoverable if an email fails to send — losing a lead because a mail server had a bad afternoon is not an acceptable failure mode. And a booking calendar has to know which slots are already taken, which is state by definition.

So there is a PostgreSQL database now, and a small admin panel we built ourselves — one account, ours. Not a CMS: there is no plugin directory, no theme layer, no marketplace, no second CMS pretending to be a platform. It does exactly four things, and everything it does not do is surface it does not have.

The pages are still static. The database only backs the parts that genuinely have state.

The honest version of the lesson: "no database" was a proxy for what we actually wanted, which was a small attack surface. Those are not the same goal, and confusing them would have cost us a site that nobody could update.

The second thing we got wrong: where it runs

The plan said deploy to Vercel, deliberately away from our own servers. The reasoning was isolation — our VPS runs client production systems, and a marketing site sharing a box with them means a compromise of one is a foothold into the other.

The reasoning was right. The conclusion was not, because the WordPress site being replaced was already on that box. Moving to Vercel would not have created isolation; it would have removed one PHP application and left everything else exactly as it was.

So the site runs on our own VPS, and the isolation is built rather than bought:

  • It runs as its own system user, not the domain owner who owns the old WordPress files.
  • It has its own PostgreSQL role, with access to its own database and nothing else.
  • It binds to loopback only. Apache is the only thing that can reach it, and the only thing exposed to the internet.
  • It is a hardened systemd service with a read-only filesystem apart from the paths it genuinely writes to.

That is a more useful kind of isolation than "somebody else's platform", because we can point at each boundary and say what enforces it.

What we did not do

We did not migrate the WordPress database. There was nothing in it worth carrying — the content was rewritten, and importing an old post table is how you inherit somebody's old problems.

We did not keep the theme's markup "so the SEO doesn't change". The redirects preserve the URLs, which is the part search engines care about; the HTML underneath was rewritten as semantic markup with one h1 per page and headings that mean something.

And we did not put a chat widget on it that costs money per message. The assistant on this site answers from the same content files the pages are built from. It cannot invent a price, because it has no ability to invent anything.

The test we apply

If your site is on a purchased theme and you are not certain what is running underneath it, ask two questions. What is the smallest thing this site actually has to do? And what am I exposing to the internet that does not serve that?

For most business sites the gap between those answers is large, and it is where the incidents come from.

We rebuilt our own site to close it, and we do the same work for clients. If yours is on WordPress and you are not sure what state it is in, we are happy to look at it with you.

Scritto da

Injamam AhmedFounder