Skip to content

Behind the Scenes

Blast radius: hardening EasyRunner after a real compromise

A Hetzner abuse email is not how you want to learn that one of your own apps has joined a botnet.

We have indications that there was an attack from your server.

That was the gist of it — one flat sentence from Hetzner's abuse desk, the polite version of something on your box is attacking other machines. The culprit turned out to be a small personal Next.js app — a private search engine over my own notes and documents — that I'd deployed months earlier and mostly forgotten about, popped by a critical Next.js RCE and quietly scanning the internet for nine days before an outside party noticed.

This post is the whole account — the alert, tracking down which app it was, patching and rotating and redeploying it. But the part I actually care about is the last bit: what's now going into EasyRunner so that the next time a hosted app gets breached, it can't take the whole server down with it. Because it will happen again. To someone. Maybe to you.

Self-hosting Next.js shouldn't break on every deploy

You move a Next.js app off Vercel onto your own VPS, it works, and then every redeploy quietly breaks something — ISR goes cold, the browser 404s on chunks that no longer exist, next/image stops optimising, streaming stalls, NEXT_PUBLIC_ values come out wrong, and in-flight requests get cut mid-deploy. I kept hitting these, so I made Next.js a first-class path in EasyRunner and closed the gaps one by one.