← All posts

Securing a static site: CSP, headers and .htaccess

You often hear that a static site has "nothing to hack": no database, no admin panel, no password to steal. That's partly true — and it's exactly why its security gets neglected. Yet a static site still has a real attack surface: content injection, clickjacking, leaking sensitive files, third-party dependencies that track your visitors. Here are the concrete settings I apply systematically, and that I put on this very site.

A strict CSP, the number-one reflex

The Content Security Policy is the measure with the most impact. It tells the browser what it's allowed to load — and blocks everything else. I start from a baseline that forbids everything, then allow only the bare minimum.

Header set Content-Security-Policy "default-src 'none'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; connect-src 'self'; base-uri 'self'; frame-ancestors 'none'; object-src 'none'; upgrade-insecure-requests"

default-src 'none' shuts the door by default. Then script-src 'self' only allows my own scripts (so no injected inline script runs), frame-ancestors 'none' prevents the site from being embedded in an iframe, and object-src 'none' neutralises plugins. If a content flaw ever slipped in somewhere, the browser would still refuse to execute foreign code.

The headers that close the doors

A few extra headers eliminate whole classes of attacks for a handful of lines:

  • HSTS forces HTTPS on every subsequent visit.
  • X-Frame-Options: DENY blocks clickjacking.
  • X-Content-Type-Options: nosniff stops the browser from "guessing" a file type.
  • Referrer-Policy limits what gets sent to external sites.
  • Permissions-Policy disables camera, microphone, geolocation… which the site doesn't need.

None of them has a visible cost for the visitor, and together they close off most of the classic vectors.

Don't expose anything by accident

The most common leak on a static site is a stray file: a .bak backup, a .git folder, a config file. Two rules solve it — block hidden files and sensitive extensions, and turn off directory listing:

Options -Indexes
<FilesMatch "^\.">
  Require all denied
</FilesMatch>
<FilesMatch "\.(bak|old|sql|env|log|ini|yml|map)$">
  Require all denied
</FilesMatch>

I add a clean error page with ErrorDocument 404 /404.html: the user stays on the site, and the server configuration isn't revealed.

Fewer third parties = less risk

Every external resource is a dependency you don't control — and often a tracker. On this site I self-host the fonts instead of loading them from a CDN: no data (not even the visitor's IP) is sent to a third party, the CSP can stay locked to 'self', and the page loads faster. No cookies, no analytics, no ad script: less surface, less compliance to handle, and better privacy.

In short

Securing a static site takes about an hour, costs nothing in performance, and can be checked in seconds with a tool like securityheaders.com or Mozilla's Observatory. It's exactly the kind of finishing touch I put on every project: it's not flashy, but it's what separates a site that "works" from one that's shipped properly.

Got a project in mind? Let’s talk.

Get in touch