Security Headers Explained: A Practical Guide for NZ Sites
Plain-language guide to HTTP security headers for NZ sites: what CSP, HSTS, X-Frame-Options, Referrer-Policy and Permissions-Policy do, with copy-paste fixes.
Quick answer
Security headers are short instructions a web server sends with every page that tell the browser what it is allowed to do. The five worth setting on every New Zealand school or small-business site are Content-Security-Policy, Strict-Transport-Security, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, and Permissions-Policy. Each is one or two lines of server config; together they close the most common browser-side attacks without changing your code.
If you want to check your own site in ten seconds first, run the Free Website Security Check — it checks all of these headers plus TLS, cookies, exposed files, and mixed content.
What these security headers actually do
Every time your browser loads a page, your web server has a chance to send back a few extra instructions in the response — before any HTML, JavaScript, or image is delivered. These instructions are called HTTP response headers. Most of them are invisible to you; a few affect what the browser is allowed to do on your behalf.
The five that matter most for a public-facing website are:
| Header | What it stops | One-line description |
|---|---|---|
| Content-Security-Policy (CSP) | Cross-site scripting (XSS), malicious iframes | Tells the browser which sources of script, style, and images are allowed |
| Strict-Transport-Security (HSTS) | Downgrade attacks, cookie theft over plain HTTP | Forces the browser to use HTTPS for a set period |
| X-Frame-Options | Clickjacking (your page being loaded inside a malicious frame) | Stops your site being embedded on someone else's page |
| X-Content-Type-Options | MIME-type sniffing attacks | Tells the browser to honour the declared content type, not guess |
| Referrer-Policy | Leaking student or staff names in the Referer header | Controls how much of the URL gets sent when users click a link |
| Permissions-Policy | Third-party scripts hijacking camera, mic, or location | Lets you turn off powerful browser features your site does not use |
None of these headers fix a vulnerability in your code. They limit what an attacker can do if something else goes wrong. Think of them as seatbelts — they are cheap, they do not stop the crash, but they change the outcome.
For dedicated reading on the headers themselves, the Mozilla Web Security guidelines remain the most accurate plain-English reference.
Why this matters for New Zealand schools and small businesses
Most New Zealand schools and small businesses are not targeted by sophisticated attackers. They are targeted by opportunistic scanning — automated bots that crawl the public web looking for sites with missing or weak security headers (1). The bots do not care how big your school is. They care that you forgot to set a header in 2019.
Three reasons this matters more than it might first appear:
- The school website is a parent-facing surface. A missing header is not a private technical detail — it is a public signal that the site, and therefore the data and people connected to it, may not be looked after carefully.
- Student data is in scope of the Privacy Act 2020. If a breach can be traced back to a misconfiguration that a single header would have prevented, that is a defensibility problem for the school, even though the legislation does not name headers specifically (2).
- CERT NZ and the NCSC both name header hygiene as a baseline control. "Baseline controls" are the things the agencies expect every New Zealand organisation to have done already before an incident is investigated (1)(3).
Setting the five recommended headers is a one-day job for whoever runs your hosting. After that, the bots stop flagging your site as a soft target, and you have a stronger starting position if something else goes wrong.
If you are responsible for a school's broader digital footprint, the Building Cyber-Safe Habits as an NZ Educator guide covers the wider context.
Content-Security-Policy — the most important one
Content-Security-Policy (CSP) is the most powerful header on this list, and the most confusing to write. It tells the browser which sources of script, style, images, and connections your site is allowed to load. Anything not explicitly allowed gets blocked.
A reasonable starting CSP for a school or small-business site looks like this:
Content-Security-Policy: default-src 'self'; img-src 'self' data:; script-src 'self'; style-src 'self' 'unsafe-inline'; connect-src 'self'; frame-ancestors 'none'; base-uri 'self'; form-action 'self'
In plain language:
default-src 'self'— by default, only load things from your own domainimg-src 'self' data:— images from your domain, plus inline data URIs (small icons)script-src 'self'— JavaScript only from your domain (no third-party trackers, no inline scripts)style-src 'self' 'unsafe-inline'— CSS from your domain, with inline styles allowed (common for school sites using inline style blocks)frame-ancestors 'none'— nobody can embed your site in an iframe (this replaces the olderX-Frame-Optionsheader)form-action 'self'— forms can only post back to your own domain
You will likely need to relax one or two directives to match what your site actually loads. A school site that embeds a YouTube video, for example, needs frame-src https://www.youtube.com. The right way to write a CSP is to start strict, then loosen it one directive at a time, testing as you go.
A CSP that breaks the site is worse than no CSP. If you are not sure, deploy the policy in report-only mode first — change Content-Security-Policy to Content-Security-Policy-Report-Only and the browser will send violation reports to a URL you specify instead of actually blocking anything.
For a deeper walkthrough of CSP unsafe-inline, unsafe-eval, and wildcard directives, see the CSP Deep Dive guide.
Strict-Transport-Security — the quick win
Strict-Transport-Security (HSTS) is the easiest header on this list to get right. One line tells the browser: for the next year, only ever load this site over HTTPS, even if a user types http:// or follows an http:// link (4).
Strict-Transport-Security: max-age=31536000; includeSubDomains
In plain language:
max-age=31536000— one year, in seconds. Any future visit within that year will refuse to fall back to HTTPincludeSubDomains— applies to every subdomain (e.g.portal.yourschool.nz). Only add this if you are certain every subdomain is HTTPS-ready
Once a browser has received HSTS, it will silently rewrite any http:// requests to https:// for that period. This blocks a category of attack where an attacker briefly forces a visitor over plain HTTP on a coffee-shop network to capture credentials.
Once you have HSTS in place, the remaining headers are mostly set-and-forget — they do not change between visits the way HSTS does.

X-Frame-Options and X-Content-Type-Options — the classic guards
These two headers are older than CSP but are still worth setting for older browsers and as defence-in-depth.
X-Frame-Options: DENY
X-Content-Type-Options: nosniff
X-Frame-Options: DENY stops your page being loaded inside an iframe on another site. The attack it prevents is clickjacking — an attacker overlays your site invisibly inside a frame, then tricks the user into clicking something they did not intend to click. If you have already set CSP's frame-ancestors 'none' (recommended), this header is technically redundant, but browsers older than CSP support will still respect it (5).
X-Content-Type-Options: nosniff tells the browser: do not try to guess what kind of file this is. Without it, a clever attacker can sometimes persuade the browser to interpret a .txt file as a script. With nosniff set, the browser will only execute a file if its declared content type matches what is being executed (5).
Both headers cost nothing to set and never break anything. Set them on every site you manage.
Referrer-Policy and Permissions-Policy — the modern pair
These two are newer and often missed. Both default to nothing, which means most browsers fall back to "share as much as possible" or "everything allowed" behaviour.
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=(), usb=()
Referrer-Policy: strict-origin-when-cross-origin means: when a user clicks a link from your site to another site, only send the origin (e.g. https://yourschool.nz) in the Referer header, not the full URL. This matters if your URLs include anything sensitive — student IDs, assessment names, search query strings (6).
Permissions-Policy lets you turn off browser features your site does not need. The example above disables camera, microphone, geolocation, payment, and USB on every page. If you do not embed a video-conference widget, you should not be handing camera permission to any script that loads.
A more permissive version for a school that uses Google Meet might be:
Permissions-Policy: camera=(self "https://meet.google.com"), microphone=(self "https://meet.google.com"), geolocation=(), payment=(), usb=()
That allows Meet to access camera and microphone, but only when embedded on your domain; everything else is disabled.
Copy-paste fixes for the four most common setups
Below are minimal, tested snippets for the four stacks most New Zealand schools and small businesses run on. Paste them, restart the relevant service, and the headers will be live.
nginx
Add inside your server { ... } block:
add_header Content-Security-Policy "default-src 'self'; img-src 'self' data:; script-src 'self'; style-src 'self' 'unsafe-inline'; connect-src 'self'; frame-ancestors 'none'; base-uri 'self'; form-action 'self'" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Frame-Options "DENY" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=(), usb=()" always;
Reload with nginx -s reload (or sudo systemctl reload nginx on systemd hosts).
Apache
Add inside your <VirtualHost>, or in an .htaccess in the document root:
<IfModule mod_headers.c>
Header always set Content-Security-Policy "default-src 'self'; img-src 'self' data:; script-src 'self'; style-src 'self' 'unsafe-inline'; connect-src 'self'; frame-ancestors 'none'; base-uri 'self'; form-action 'self'"
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
Header always set X-Frame-Options "DENY"
Header always set X-Content-Type-Options "nosniff"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Header always set Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=(), usb=()"
</IfModule>
If the headers disappear on Apache, the most common cause is a mod_headers mismatch with how the site is hosted — confirm mod_headers is loaded (apache2ctl -M | grep headers).
Express (Node.js)
Use the widely-trusted helmet package:
npm install helmet
Then in your app:
import helmet from 'helmet';
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
imgSrc: ["'self'", 'data:'],
scriptSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"],
connectSrc: ["'self'"],
frameAncestors: ["'none'"],
baseUri: ["'self'"],
formAction: ["'self'"],
},
},
strictTransportSecurity: { maxAge: 31536000, includeSubDomains: true },
referrerPolicy: { policy: 'strict-origin-when-cross-origin' },
}));
helmet covers all six headers plus several others automatically.
Cloudflare
If you front your site with Cloudflare, you can set most of these from the dashboard:
- Go to SSL → Edge Certificates → HSTS → enable with
max-age=31536000,includeSubDomains,preload(read the warning before submitting to the preload list) - Go to Rules → Transform Rules → Modify Response Headers → add a rule matching all requests and setting the other five headers
- Alternatively, set them at origin and Cloudflare will pass them through
If you only set headers at Cloudflare and not at origin, your site loses those headers the moment Cloudflare is bypassed (e.g. for a direct API call). Set them at both layers where you can.
Verify your headers before saying you're done
Setting the headers is half the job. Verifying them is the other half. Two ways to check:
1. Run the free NZAI scanner
The Free Website Security Check at nzscan.nzaisecurity.com checks all five headers in about ten seconds, alongside TLS, exposed files, cookies, and mixed content. If any header is missing, the report tells you which snippet to paste for nginx, Apache, Express, or Cloudflare.
2. curl from the command line
For a quick sanity check without opening a browser:
curl -sI https://yoursite.nz | grep -iE "content-security-policy|strict-transport-security|x-frame-options|x-content-type-options|referrer-policy|permissions-policy"
You should see all six header names in the output. If any are missing, you have a deploy problem — either the server did not reload, or a caching layer (Cloudflare, Varnish, a CDN) is stripping them. To bypass cache for the test, add a ?nocache=1 query string or run against a fresh URL.
3. Browser DevTools
Open your site in Chrome or Firefox, press F12 to open DevTools, switch to the Network tab, click any page load, and inspect Response Headers in the right pane. All six should be visible. This is what an attacker sees — and what a real visitor's browser sees.
Common header mistakes to avoid
From running the free scanner across New Zealand school and small-business sites, the most frequent missteps are:
- Setting a header but a CDN strips it. Cloudflare, Varnish, and some reverse proxies overwrite or drop headers set at the origin. Always verify through the CDN edge, not just from the origin server.
- CSP that uses
unsafe-inlineandunsafe-evaleverywhere. Defeats most of what CSP is for. If you cannot remove them, scope them to specific directives (style-src 'unsafe-inline'is more acceptable thanscript-src 'unsafe-inline'). - HSTS sent over plain HTTP. HSTS only works over HTTPS. If your site is still reachable over HTTP for any reason, HSTS will not fire.
- CSP report-only mode left on forever. Report-only mode does not protect the site. Once you have triaged the violation reports, switch back to enforcement mode (
Content-Security-Policy, notContent-Security-Policy-Report-Only). - Wildcard
*inscript-srcorframe-ancestors.script-src *is almost the same as no CSP.frame-ancestors *allows any site to frame yours. Always be explicit. - Setting the headers in
local devbut not in production config. The build environment that runs on your laptop is rarely the same as the one that ships. Verify on the live URL.
Knowledge check
Test your understanding of HTTP security headers with these questions. Click or tap each card to flip it and see the answer.
default-src 'self'), so any third-party font CDN, form action endpoint, or embedded video domain needs its own directive (for example font-src https://fonts.gstatic.com, form-action 'self' https://forms.example.com, frame-src https://www.youtube.com). Read the browser console errors to see which directive blocked which request, then loosen only the directives that need it (5).max-age=31536000 and includeSubDomains on Friday. On Monday morning the IT team spins up a new staging.yourschool.nz subdomain on plain HTTP for testing — and it stops responding entirely. What happened, and is it fixable without consequences?tap to flipstaging.yourschool.nz and silently rewrites it to HTTPS. If the subdomain does not serve HTTPS, the user's browser shows an error. Fixing it requires serving HTTPS on the subdomain — there is no way to "undo" HSTS for affected visitors during the max-age period, which is why includeSubDomains should only be added once every subdomain is HTTPS-ready (4).Sources and references
[1] CERT NZ. (2025). Top 11 cyber security tips for your business. https://www.cert.govt.nz/business/guides/top-tips-for-cyber-security/
[2] New Zealand. Parliament. (2020). Privacy Act 2020. https://www.legislation.govt.nz/act/public/2020/0031/latest/whole.html
[3] New Zealand. National Cyber Security Centre. (2025). Cyber security baseline controls for organisations. https://www.ncsc.govt.nz/
[4] Internet Engineering Task Force. (2012). RFC 6797 — HTTP Strict Transport Security (HSTS). https://datatracker.ietf.org/doc/html/rfc6797
[5] Mozilla. (2025). MDN Web Docs — Content Security Policy (CSP). https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP
[6] Mozilla. (2025). MDN Web Docs — Referrer-Policy header. https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy
[7] Mozilla. (2025). Web security guidelines. https://infosec.mozilla.org/guidelines/web_security
[8] Chromium Project. (2025). HSTS preload list submission. https://hstspreload.org
What to do next
- Run the Free Website Security Check to see which of these headers your site is already sending, and which it is missing. The report includes the same copy-paste fixes shown above.
- For the broader educator safety pathway, including account security, devices, phishing, and student data, see Building Cyber-Safe Habits as an NZ Educator.
- For a deeper CSP walkthrough covering
unsafe-inline,unsafe-eval, nonces, hashes, and wildcards, see the CSP Deep Dive guide. - For the HSTS-specific gotchas — preload list submission,
includeSubDomainspitfalls, testing — see the HSTS guide. - For everything else on a typical NZ site (TLS, exposed files, cookies, mixed content, redirects), the free scanner covers all of it in one pass.
- HSTS Strict-Transport-Security Explained for NZ Sites — the standalone HSTS deep-dive, including preload list submission,
includeSubDomainspitfalls, and copy-paste fixes for nginx, Apache, Express and Cloudflare. - Secure Cookie Flags for NZ Sites — what HttpOnly, Secure and SameSite actually do, and copy-paste fixes for nginx, Apache and Express.
- HTTPS Everywhere: Fixing Mixed Content on NZ Sites — the mixed-content companion guide, including CSP
upgrade-insecure-requests, browser auto-upgrade, andwww↔ apex redirect handling. - Redirect Chains Explained for NZ Sites — why long or insecure redirect chains are bad, and copy-paste nginx/Apache single-301 configs.
- Security.txt for NZ Sites — RFC 9116
/.well-known/security.txt, what to put in it (and what not to), with a copy-paste example. - CSP Deep Dive for NZ Sites — the full Content-Security-Policy walkthrough, including
unsafe-inline,unsafe-eval, nonces, hashes and report-only mode. - Web Server Hardening for NZ Sites — how to block exposed paths like
/.env,/.git,/backup.zip,/phpinfo.phpand/server-statusso they return clean 404 instead of 403. - Environment File Leaks Explained for NZ Sites — what counts as a secret, the rotation playbook for leaked
.envfiles, scanner caveats and a secrets manager overview. - TLS Certificate Renewal Automation for NZ Sites — Certbot, Let's Encrypt, DNS-01 vs HTTP-01, and how to keep HTTPS certificates renewed automatically on NZ-hosted sites.
- Rate Limiting and Bot Protection for NZ Sites — why scan-probing differs from real attacks, with copy-paste nginx
limit_reqand fail2ban-style heuristics. - Phishing and Fake Login Forms for NZ Schools — how exposed-secret scan findings connect to phishing campaigns, with fake-login-form detection patterns for NZ schools.