Replacing WordPress with a static site I can deploy from my own CI
Retiring an unpatched WordPress on PHP 5.6 by replacing it with an Astro build pushed from a self-hosted Gitea runner to the same shared hosting.
For years cs-ol.de did exactly one useful thing, which was carry mail. The
web side was a WordPress install with no posts, titled (accurately) “Mail
only domain – really”. It had been running on PHP 5.6.40, a release that
stopped getting security patches in December 2018.
WordPress core itself was fine. 6.2.11 is a maintained security branch, and every core file on the server was byte-identical to the official release. The trouble was underneath. A current CMS on an interpreter that last saw a security fix seven years ago is still an internet-facing PHP application you’re not patching, and it was running on an account whose real value is the mailbox attached to it.
Having no content doesn’t make it harmless. Nobody was ever going to steal the zero blog posts; the mailbox is what an attacker would be after.
What replaced it
A static site, with no server-side application at all. I didn’t want a lighter CMS. The pieces are these:
- Astro builds Markdown into plain HTML.
- The source lives in a Gitea repository on my homelab cluster.
- Gitea Actions builds it and syncs the output to the same shared hosting.
- LiteSpeed serves static files, so PHP, MySQL and the WordPress attack surface stop mattering. I don’t have to keep them updated because nothing uses them.
What I like most about this setup is how it changes the credentials question. Publishing used to mean holding a password to a live admin panel. Now it means commit access to a git repository, behind a pull request and a merge button.
What I had to check first
Two things I found changed the design, and I couldn’t have guessed either of them from the outside.
The first was that the www → apex redirect came from WordPress. The
response carried x-redirect-by: WordPress. Delete WordPress and the
canonicalisation disappears along with it, without any warning. So the new
.htaccess has to recreate a redirect nobody had realised was an application
feature.
The second was that /.well-known/acme-challenge/ is live and returns
200. The hosting account’s own certificate, a Let’s Encrypt *.cs-ol.de,
validates through that path. My first draft of the .htaccess denied all
dotfiles, which would have blocked it. Nothing would have broken that day. The
certificate would just have failed to renew weeks later, and I’d have had no
obvious reason to connect that to a website migration.
Both come down to the same problem. Something I was deleting turned out to be quietly holding up something else I wasn’t thinking about.