Skip to content
NewHost
Menu

HTTP Security Headers for Next.js and Node.js Apps - A Guide

Which HTTP security headers to set on a Next.js or Node.js app - HSTS, CSP, nosniff, Referrer-Policy and frame protection - with safe rollout steps.

By NewHost team · · 6 min read

HTTP security headers are instructions your app sends with every response that tell the browser to be stricter: only use HTTPS, don't guess file types, don't let other sites frame your pages, and only run scripts from places you trust. They take an afternoon to set up, cost nothing in performance and close off whole classes of attack. This guide covers the headers worth setting, how to add them in Next.js and Express, and how to roll out the strict ones without breaking your site.

The headers that matter

Header What it does A sensible value
Strict-Transport-Security Tells browsers to use HTTPS only for your domain, even if someone types http:// max-age=31536000; includeSubDomains
Content-Security-Policy Limits where scripts, styles, images and frames may load from Built per site, see below
X-Content-Type-Options Stops browsers guessing a file's type, which blocks some script injection tricks nosniff
Referrer-Policy Controls how much of the current URL is sent to other sites when users click links strict-origin-when-cross-origin
Permissions-Policy Switches off browser features you don't use, such as camera or geolocation camera=(), microphone=(), geolocation=()
X-Frame-Options Stops other sites from showing your pages in a frame (clickjacking) DENY or SAMEORIGIN

You may see older advice to set X-XSS-Protection. Modern browsers have removed that filter, and a Content Security Policy does the job properly, so you can leave it out.

It is also worth removing headers that only help attackers. Next.js sends X-Powered-By: Next.js and Express sends X-Powered-By: Express unless you turn them off.

Adding headers in Next.js

Next.js lets you set response headers for any path in next.config.ts:

import type { NextConfig } from "next";

const securityHeaders = [
  { key: "Strict-Transport-Security", value: "max-age=31536000; includeSubDomains" },
  { key: "X-Content-Type-Options", value: "nosniff" },
  { key: "Referrer-Policy", value: "strict-origin-when-cross-origin" },
  { key: "Permissions-Policy", value: "camera=(), microphone=(), geolocation=()" },
  { key: "X-Frame-Options", value: "DENY" },
];

const nextConfig: NextConfig = {
  poweredByHeader: false,
  async headers() {
    return [{ source: "/:path*", headers: securityHeaders }];
  },
};

export default nextConfig;

These headers apply to pages, API routes and static files alike. If one route genuinely needs to be framed, for example an embeddable widget, add a more specific rule for that path with SAMEORIGIN or a CSP frame-ancestors list, rather than weakening the whole site.

Adding headers in Express

For an Express API or server-rendered app, the Helmet middleware (npm install helmet) sets a sensible collection of security headers in one line, including HSTS, nosniff, frame protection and a default Content Security Policy, and removes X-Powered-By:

import express from "express";
import helmet from "helmet";

const app = express();
app.use(helmet());

app.get("/health", (req, res) => res.json({ ok: true }));

app.listen(process.env.PORT || 3000);

Helmet's default CSP is strict. For a pure JSON API that's ideal. For an app that serves HTML with third-party scripts, configure the CSP directives yourself, as described next.

Content Security Policy without breaking your site

A Content Security Policy (CSP) is the most powerful header and the one most likely to break something, because it blocks any script, style, font, image or connection you didn't list. MDN's Content Security Policy reference documents every directive. A typical starting point for a site that loads nothing from other domains looks like this:

default-src 'self';
script-src 'self' 'unsafe-inline';
style-src 'self' 'unsafe-inline';
img-src 'self' data: https:;
font-src 'self';
connect-src 'self';
frame-ancestors 'none';
base-uri 'self';
form-action 'self';
object-src 'none'

Send it as one line, with the directives separated by semicolons. Some notes:

  • 'unsafe-inline' for scripts is needed by many Next.js setups, because the framework adds small inline scripts to each page. The stricter alternative is a nonce generated per request, which the Next.js Content Security Policy guide explains. Nonces make every page dynamic, so weigh that against your caching.
  • Third parties must be listed. Analytics, chat widgets, payment pages, embedded maps and video each need their domains in the right directive. List them deliberately; that list doubles as an inventory of who can run code on your site.
  • frame-ancestors 'none' is the modern equivalent of X-Frame-Options: DENY. Setting both is fine.

Roll out in report-only mode first

Instead of switching the policy on straight away, send it as Content-Security-Policy-Report-Only. The browser then reports violations in the developer console (and to a reporting endpoint if you configure one) but blocks nothing. Click through your whole site, including forms, checkout and pages with embeds, fix the policy until the console is clean, then rename the header to Content-Security-Policy.

Be careful with HSTS

Strict-Transport-Security is excellent, and it is the one header you cannot quickly undo. Once a browser has seen it, it refuses plain HTTP for your domain until max-age expires. Before you set it:

  • Make sure every page works over HTTPS, and that HTTP redirects to HTTPS. Our guide to www or non-www and HTTPS redirects covers the redirect side.
  • If you add includeSubDomains, every subdomain must have a valid certificate too, including old ones like a forgotten test. site. Check your DNS records first.
  • Consider starting with a short max-age (for example a day) and raising it once you are confident.
  • Only add preload and submit your domain to the browser preload list if you understand that removal takes a long time.

Free Let's Encrypt certificates make HTTPS on every hostname straightforward. If you're unsure how certificates work, read SSL certificates explained.

Checking your headers

From a terminal:

curl -sI https://www.example.co.za/ | grep -iE "strict-transport|content-security|x-content-type|referrer-policy|permissions-policy|x-frame"

Or open your browser's developer tools, go to the Network tab, click the first document request and look at the response headers. Check a page, an API route and a static file, because some setups apply headers to only one of them.

Avoid duplicate headers

If a reverse proxy or the hosting panel already adds a header and your app adds it again, browsers may see two values. For CSP, two policies both apply, so the result is stricter than either. Pick one place to set each header, ideally your application code, where it is versioned with everything else.

Where headers fit in your security work

Headers protect users' browsers. They don't fix vulnerable dependencies, weak passwords or missing access checks in your code. Use them alongside the rest of our Node.js security checklist and the items in the Next.js production checklist.

Frequently asked questions

Do security headers affect SEO or speed?

Not measurably. They are a few hundred bytes per response. HTTPS itself is a ranking signal, and HSTS helps make sure visitors always land on the HTTPS version.

Which header should I add first?

X-Content-Type-Options, Referrer-Policy and frame protection are safe to add straight away. Add HSTS once HTTPS works everywhere, and CSP last, in report-only mode first.

Can I set headers in the hosting control panel instead of my code?

Often you can, but setting them in the app keeps them in Git, makes them apply the same way on every environment and avoids duplicates. Use one place, not both.

Why does my CSP block Google Analytics or a chat widget?

Because their script and connection domains are not in your policy. Add the domains the provider documents to script-src, connect-src and, if needed, img-src and frame-src.

Run your Next.js or Node.js app on NewHost: South African servers, free Let's Encrypt SSL on your own domain and Git deploys, so the headers you set in code ship with every push.

Related guides

Ready to launch on NewHost?

Choose a plan and go live today, or tell us what you need and we'll recommend the right setup.