The 7 Security Headers Your Web App Is Probably Missing
Most developers don't think about security headers until something goes wrong. That's not a criticism; it's just how modern frameworks work. Next.js, Nuxt, SvelteKit, and Rails all ship without most security headers configured. If you're building with AI coding tools like Cursor, Copilot, or Windsurf, the gap is even wider, because those tools generate functional code, not hardened code.
The good news is that fixing this takes about five minutes. These seven headers cover the vast majority of browser-level protections, and once they're set, they protect every single user who visits your site.
Content-Security-Policy (CSP)
CSP tells the browser exactly where your page is allowed to load scripts, styles, images, and other resources from. Without it, an attacker who finds a cross-site scripting (XSS) vulnerability can inject a script tag pointing to any domain, and the browser will happily execute it.
A good starting point for most web apps:
Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self'; connect-src 'self'; frame-ancestors 'none'
This locks scripts to your own domain, allows inline styles (which most frameworks need), permits images from HTTPS sources, and blocks your site from being framed. You'll likely need to adjust this as you add third-party services, but starting strict and loosening is far safer than starting open.
In Next.js, you can set this in your next.config.js:
const securityHeaders = [
{
key: 'Content-Security-Policy',
value: "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self'; connect-src 'self'; frame-ancestors 'none'"
}
];
module.exports = {
async headers() {
return [{ source: '/(.*)', headers: securityHeaders }];
},
};
Strict-Transport-Security (HSTS)
HSTS tells browsers to only ever connect to your site over HTTPS. Without it, a user's first visit might happen over plain HTTP before your server redirects them to HTTPS. That initial unencrypted request is enough for an attacker on the same network to intercept traffic or redirect the user to a spoofed version of your site.
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
The max-age value is in seconds. 31536000 is one year. includeSubDomains applies the rule to all subdomains (make sure they all support HTTPS first). preload lets you submit your domain to the browser preload list so even the very first visit is forced to HTTPS.
Once you set this header, you're committing to HTTPS for the duration of max-age. Make sure your certificate renewal is working before you turn it on.
X-Content-Type-Options
This one is simple. Set it to nosniff and move on.
X-Content-Type-Options: nosniff
Without it, browsers will try to guess the MIME type of files they receive. This sounds harmless, but it means a file served as text/plain could be reinterpreted as JavaScript and executed. Setting nosniff tells the browser to trust the Content-Type header you send and nothing else.
X-Frame-Options
This header controls whether other sites can embed yours in an iframe. Without it, your site is vulnerable to clickjacking, where an attacker overlays your page with invisible elements to trick users into clicking things they didn't intend to.
X-Frame-Options: DENY
DENY blocks all framing. If you need to allow your own site to frame itself (for example, if you use iframes internally), use SAMEORIGIN instead. If you've already set frame-ancestors 'none' in your CSP, this header is technically redundant in modern browsers, but older browsers don't support CSP, so it's worth setting both.
Referrer-Policy
When a user clicks a link on your site to go somewhere else, the browser sends a Referer header telling the destination where the user came from. This can leak sensitive URL paths, query parameters, or internal page structures.
Referrer-Policy: strict-origin-when-cross-origin
This is the sensible default. It sends the full URL for same-origin requests (your own analytics and internal navigation work fine) but only sends the origin (just the domain, no path) for cross-origin requests. Your internal URLs stay private.
Permissions-Policy
Permissions-Policy lets you disable browser features your site doesn't need. If your app doesn't use the camera, microphone, or geolocation, you should explicitly disable them. This way, even if an attacker injects code into your page, they can't access those APIs.
Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=()
The empty parentheses mean "nobody gets access, not even this page." Only add permissions you actually use. Most web apps need none of these.
X-XSS-Protection
This one has a complicated history. Older browsers shipped with a built-in XSS filter that tried to detect and block reflected XSS attacks. It sounded good in theory, but the filter itself had bypasses and could actually be exploited to create XSS vulnerabilities that wouldn't otherwise exist.
Modern browsers have removed the filter entirely. The current best practice is:
X-XSS-Protection: 0
Setting it to 0 explicitly disables the old filter for any users still on older browsers. Your CSP header handles XSS mitigation now, and it does a far better job.
How to check yours
You can check your headers right now by opening your browser's developer tools, going to the Network tab, reloading the page, and clicking on the main document request. The response headers are listed there. Look for each of the seven headers above.
If you want to check all of them in one go, paste your URL into paste your URL into paste your URL into Hexora at hexora.uk and you'll have a full report in under 60 seconds, including exactly which headers are missing and what values to set.
Five minutes, every user protected
Security headers are the single highest-impact, lowest-effort security improvement you can make to a web application. You're not refactoring code, adding dependencies, or changing your architecture. You're adding a few lines of configuration that protect every user on every page load.
If you're shipping fast with AI coding tools, take five minutes before your next deploy. Your framework won't do this for you, but it only needs to be done once.
Worried about your own site's security? Get a free scan in seconds.
Scan your site for free