← Blog/Security

5 Security Headers Every Indie Dev Should Set (And How to Check Them)

Security headers are free. They take 5 minutes to add. And most indie dev projects ship without a single one. Here are the 5 that matter, what each one does, and how to verify they're actually working.

·8 min read

Most web app security vulnerabilities require sophisticated attacks — SQL injection, XSS, CSRF, token theft. Security headers are different: they're not a defense against sophisticated attacks. They're a defense against browsers doing things they shouldn't do by default.

Every major browser respects these headers. Setting them takes 5 minutes. And if your app gets security-scanned, missing headers are the first thing the report lists — because they're easy to find, easy to fix, and embarrassingly common to miss.

Here are the 5 you need. For each: what it does, what happens without it, and the exact line to add it.


1. Content-Security-Policy (CSP)

What it does: CSP tells browsers which sources are allowed to load scripts, styles, images, fonts, and other resources on your page. It's the primary defense against Cross-Site Scripting (XSS) attacks.

What happens without it: If an attacker manages to inject a <script> tag into your page — through a user-generated content field, a third-party library vulnerability, or a compromised CDN — the browser executes it without question. With no CSP, browsers will happily run any script from any origin.

A real-world example: an indie dev uses a third-party comment widget that gets compromised. The widget starts serving a cryptominer script. Without CSP, every visitor's browser runs it. With CSP, the browser blocks any script not explicitly allowed.

The one-liner (Next.js — add to next.config.js headers or middleware):

Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self'; connect-src 'self' https:

This is a moderate starting policy. For tighter security, remove 'unsafe-inline' from script-src and use nonces (Next.js middleware makes this easy). Start with the above; tighten it after you verify nothing breaks.

Verification: Open DevTools → Network → reload → click your HTML document → Response Headers. Look for Content-Security-Policy. Or run a free Scantient scan — it checks CSP presence and reports the policy value.


2. X-Frame-Options

What it does: Prevents your app from being embedded in an <iframe> on another website. This is the primary defense against clickjacking attacks.

What happens without it: An attacker creates a transparent iframe overlaying your app on their malicious website. The user thinks they're clicking a button on the attacker's page — they're actually clicking a button on your app. This has been used to steal OAuth tokens, trigger purchases, and execute account actions without user consent.

Clickjacking is old, but it's still in the wild because it's easy to execute and most indie apps don't need to be embeddable anywhere.

The one-liner:

X-Frame-Options: DENY

Use DENY unless you specifically need your app to be embeddable in iframes from trusted origins (in which case use SAMEORIGIN or the newer CSP frame-ancestors directive). Most indie apps should use DENY.

Verification: Same as above — Response Headers in DevTools, or Scantient flags missing X-Frame-Options as a medium-severity finding.


3. Strict-Transport-Security (HSTS)

What it does: Tells browsers to always use HTTPS for your domain — even if a user types http:// or clicks an old HTTP link. Once a browser sees this header, it upgrades all future requests to HTTPS automatically for the duration you specify.

What happens without it: Your app is vulnerable to SSL stripping attacks. An attacker on the same network intercepts the initial HTTP request before it can redirect to HTTPS, and serves a fake version of your site over plain HTTP. Your users never see the HTTPS redirect — they're talking to the attacker the whole time.

If you're already on HTTPS (and you should be — Vercel and Netlify give you this for free), HSTS is the final piece that ensures users can't be downgraded.

The one-liner:

Strict-Transport-Security: max-age=31536000; includeSubDomains

max-age=31536000 is one year — the browser remembers to always use HTTPS for your domain for that long. includeSubDomains applies it to all subdomains. Add preload when you're confident your entire domain and all subdomains are on HTTPS permanently.

Note: Only send HSTS over HTTPS — never over HTTP. Your hosting provider likely handles this automatically if you configure it in headers.


4. X-Content-Type-Options

What it does: Stops browsers from "sniffing" MIME types. Without it, browsers may guess what type a resource is and execute it as code even if your server says it's something else.

What happens without it: Say a user uploads a file to your app. Your server saves it as application/octet-stream. A malicious user uploads an HTML file and tricks a victim into loading the URL. Without this header, some browsers sniff the content, decide it looks like HTML, and render it as a webpage — potentially executing embedded scripts. This is a MIME confusion attack.

It's also relevant for JSON API responses — a misconfigured server might accidentally serve JSON with a content type that a browser decides to execute.

The one-liner:

X-Content-Type-Options: nosniff

That's the entire header. One value, no options. Every app should have this. It takes 10 seconds to add and there's no downside.


5. Referrer-Policy

What it does: Controls how much information about the source URL is included in the Referer header when users navigate from your site to external links. By default, browsers send the full URL — including query parameters — as the referrer.

What happens without it: A user visits https://yourapp.com/reset-password?token=abc123 and clicks an external link. The destination site receives that full URL in its server logs, including the password reset token. This is a real data leak vector — not theoretical.

The same applies to search query parameters, session identifiers in URLs, user IDs, and any other sensitive data that ends up in URLs (which it shouldn't, but often does).

The one-liner:

Referrer-Policy: strict-origin-when-cross-origin

This sends the origin (scheme + domain, no path or query) when navigating cross-origin, and the full URL for same-origin navigation. It's the sensible default that preserves analytics (cross-origin navigation still sends your domain as referrer) while preventing URL parameter leakage.


How to Add All 5 in Next.js (One Block)

If you're using Next.js, add these to your next.config.js:

// next.config.js
const securityHeaders = [
  {
    key: 'Content-Security-Policy',
    value: "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self'; connect-src 'self' https:",
  },
  { key: 'X-Frame-Options', value: 'DENY' },
  { 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' },
];

module.exports = {
  async headers() {
    return [
      {
        source: '/(.*)',
        headers: securityHeaders,
      },
    ];
  },
};

For other frameworks: add these as response headers in your server config (Nginx, Express, Vercel vercel.json, Cloudflare Workers, etc.). The header values are identical — only the configuration syntax differs.

Check All 5 in 60 Seconds

You can verify these headers manually using browser DevTools (Network tab → select your HTML document → Response Headers). But that's tedious and error-prone — especially if you want to check every route, not just the homepage.

Scantient checks all 5 security headers as part of its external security scan. It also checks CORS, API key exposure, rate limiting, and 20+ other signals. For a deployed app, you want to know what the internet actually sees — not what your config file says.

Run a free scan on your app and you'll know in 60 seconds exactly which headers are missing, which are misconfigured, and what to do about each one. No signup required.

Check all 5 security headers in 60 seconds

No signup. No code access. Paste your URL and get an instant security header report — plus API key exposure, CORS, rate limiting, and more.