The cheapest vulnerability to fix is the one you never expose.
Most of what I run lives behind an identity-aware proxy with a device allowlist. Nothing listens on the public internet directly; the trust boundary is the edge, and the services behind it assume they're only ever reached by someone already authenticated.
Surface is a choice, not a fact
It's tempting to treat attack surface as something you measure after the fact — scan the box, count the open ports, file the findings. But surface is decided much earlier, when you choose an architecture:
- A server that renders pages on request has a request handler for every URL.
- A static site rendered ahead of time has no request handler at all.
Both can serve the same HTML. Only one of them can be tricked by a crafted request, because only one of them is processing requests.
The tradeoff
This isn't free. You give up anything that genuinely needs per-request server logic: search, comments, dynamic personalization. The trick is to notice how little of that a blog actually needs, and to push the few things that do need a backend onto infrastructure whose surface someone else is paid to defend.
The goal isn't zero features. It's zero features that require me to run an internet-facing process.
For a site whose entire job is to hand you some HTML and a few downloads, the right amount of running code exposed to the world is none.