Static site security

When people ask me why I build marketing sites as static sites, security is near the top of my answer. Not because static sites are magically safe, but because they remove most of the things that get small business sites hacked. What is left is a short list you can actually look after.

This article covers why the attack surface is small, what you still need to protect, and a checklist to work through.

Why static sites have a small attack surface

Most website compromises target the software running on the server: outdated plugins and themes, a vulnerable version of the CMS, or a login page with a weak password.

A static site removes those targets:

  • No plugins to exploit. There is no plugin code running on the server for each visit. Features come from build-time tools or separate services.
  • No public admin login. There is no /wp-admin or equivalent on your site for bots to hammer. Editors sign in to a separate CMS service, or to your git provider.
  • No database on the site. Nothing to inject into, nothing to leak through a vulnerable query.
  • Read-only output. The host serves fixed files. Visitors cannot change them through the site.

On top of that, git gives you two things a typical CMS does not:

  • An audit trail. Every change to templates and content is a commit with an author and a time. You can see exactly what changed and who changed it.
  • Instant rollback. If something bad is published, reverting the commit and rebuilding puts the site back exactly as it was.

The risk moves from the website to the accounts and services around it. That is a much smaller and more manageable set.

Securing the repository

Your git repository is now the source of truth. Anyone who can push to it can change the site.

  • Turn on two-factor sign-in for everyone with access to your git provider.
  • Give people the least access they need. Most do not need admin rights on the repository.
  • Protect the main branch, so changes go through a pull request or at least cannot be force-pushed.
  • Review which apps and integrations have access to the repository, and remove ones you no longer use.
  • Never commit secrets such as API keys. Use your host’s environment variables instead.

Securing your CMS and editors

If editors use a CMS, it can write to your site, so treat its sign-ins seriously.

  • Prefer a CMS where editors have their own accounts, rather than sharing a git login or an admin password.
  • Use roles, so most editors can change content but not settings.
  • Limit each editor to the sites they work on.
  • Require two-factor sign-in at least for admins.
  • Check what access the CMS asks for on your repository. Read and write access to contents is normal; broader access deserves a question.
  • Remove people promptly when they leave a project.

Because a git-based CMS writes commits, its changes appear in the same history as everyone else’s, so the audit trail covers editors too.

Forms and spam

Forms are usually the only part of a static site that accepts input from the public, so they deserve attention.

  • Use a form service or function that validates input and filters spam, ideally with invisible methods first.
  • Do not collect more personal information than you need.
  • Know where submissions are stored and who can read them.
  • Delete old submissions on a schedule.
  • If you write your own serverless function, treat it like any other application: validate everything and keep its dependencies updated.

See handling forms on a static site for the options.

Hosting and DNS

  • Turn on two-factor sign-in for your hosting account and your domain registrar. Losing control of DNS is one of the few ways to take over a static site entirely.
  • Use HTTPS everywhere. Most static hosts provide certificates automatically.
  • Add sensible security headers, such as a content security policy, through your host’s configuration.
  • Remove old preview deployments and unused custom domains.

Dependencies and the build

Static sites still use code at build time: the generator, plugins for the generator and JavaScript packages.

  • Keep the generator and its dependencies reasonably up to date. Unlike a live CMS, you can test updates on a branch before they reach the site.
  • Prefer fewer dependencies. Each one is code you are trusting.
  • Be careful with third-party scripts embedded in pages, such as analytics, chat widgets and embedded forms. They run in your visitors’ browsers.

Build-time dependencies are a lower risk than server plugins because they do not run when visitors load a page, but they are not zero risk.

A security checklist

  • Two-factor sign-in on git provider, host, registrar and CMS
  • Least-privilege access on the repository
  • Main branch protected
  • Unused integrations removed
  • No secrets in the repository
  • Editors have individual accounts and appropriate roles
  • Form spam filtering in place, data retention set
  • HTTPS and security headers configured
  • Dependencies reviewed every few months
  • Third-party scripts kept to a minimum

For how this compares with keeping a WordPress site safe, see maintenance: WordPress updates vs static.

My take

Static sites do not make security someone else’s problem, but they shrink it to a list of accounts and settings rather than a stream of patches. For a small team without a dedicated security person, that difference is enormous. I would rather spend an hour setting up two-factor sign-in and branch protection than every month wondering whether a plugin update has a hole in it.