Wes Ellis./ a personal notebook
Technology. Stories. Side projects.
A few things worth writing down.
← Back to Engineering

Engineering

Security Headers on a Static Site When Your Host Won't Set Them

THE SHORT VERSION4 points
  • My new static blog got an F on a header scan, because DigitalOcean's static sites can't set custom response headers.
  • Two headers can live in a <meta> tag, CSP and referrer policy, and they're the two that matter most for a blog.
  • For the full set, put a proxy like Cloudflare in front, or move to a host that reads a _headers file.
  • The grade is a signal, not a verdict. Know which missing header would actually hurt someone.

This site had been live for about an hour when I ran it through a security header scanner. It came back with a big red F.

Nothing was broken, and nothing was exposed. It's a static site: no login, no database, no forms, no server code for anyone to poke at. But the grade still stung. And if you work anywhere near security, people will run that scan on your personal site.

What the F actually means

The scanner checks for six HTTP response headers:

Header What it does Works in a <meta> tag?
Strict-Transport-Security (HSTS) Tells browsers to only ever use HTTPS No
Content-Security-Policy (CSP) Limits where scripts, styles, and images can load from Yes, minus frame-ancestors
X-Frame-Options Stops other sites from framing yours (clickjacking) No
X-Content-Type-Options Stops browsers from guessing file types No
Referrer-Policy Controls how much of your URL leaks to other sites Yes
Permissions-Policy Turns off browser features you don't use, like the camera and geolocation No

I had none of them. Not because I forgot, but because the host wouldn't let me set them.

The catch with DigitalOcean static sites

DigitalOcean's App Platform is a great home for a static site. It's cheap, it's fast, and it deploys on every push. But static site components don't let you define custom response headers. There's a feature request for it from 2024, and as of this writing it's still open.

So what can you actually do?

Fix 1: put the important ones in the HTML

Two of the six can live in a <meta> tag, and they happen to be the two that matter most for a blog:

<meta http-equiv="Content-Security-Policy" content="default-src 'self'; img-src 'self' data:; style-src 'self' 'unsafe-inline'; script-src 'self'">
<meta name="referrer" content="strict-origin-when-cross-origin">

Heads up

Know the limits, though. A meta CSP can't use frame-ancestors, so it won't stop clickjacking. And HSTS, X-Frame-Options, and the rest only work as real headers. The scanner will still grumble, but you've covered the parts that protect your readers.

Tip

Tune the CSP to what your site actually loads. If you pull in a script from a CDN (my editor page loads one), that page needs its own rule, or it'll stop working the moment you add the policy.

Fix 2: put a proxy in front

For the full set, route your domain through a proxy that can add headers. Cloudflare's free plan does this with Transform Rules.

The trade-off is moving your DNS there, which means carefully re-creating your email records (MX, SPF, DKIM, DMARC) so mail keeps flowing.

Fix 3: pick a host that reads a headers file

Some static hosts let you drop a _headers file in your build output and apply it for you. If headers matter a lot to you, that's the least fiddly option. It just means moving hosts.

Where I landed

For a personal blog, Fix 1 gets you most of the real-world benefit in ten minutes. The grade is a signal, not a verdict. What matters is knowing which missing header would actually hurt someone, and that answer is different for a static notebook than for a web app with a login page.