logoPressFast

Security Headers

Configure HTTP security headers

Security Headers

Protect your app with proper HTTP security headers.

Why Security Headers?

Security headers help protect against:

  • Cross-Site Scripting (XSS)
  • Clickjacking attacks
  • MIME type sniffing
  • Information leakage
  • Man-in-the-middle attacks

Next.js Configuration

Configure security headers in next.config.ts:

// next.config.ts
import type { NextConfig } from 'next';

const nextConfig: NextConfig = {
  async headers() {
    return [
      {
        source: '/:path*',
        headers: [
          {
            key: 'X-DNS-Prefetch-Control',
            value: 'on',
          },
          {
            key: 'Strict-Transport-Security',
            value: 'max-age=63072000; includeSubDomains; preload',
          },
          {
            key: 'X-Frame-Options',
            value: 'SAMEORIGIN',
          },
          {
            key: 'X-Content-Type-Options',
            value: 'nosniff',
          },
          {
            key: 'X-XSS-Protection',
            value: '1; mode=block',
          },
          {
            key: 'Referrer-Policy',
            value: 'origin-when-cross-origin',
          },
          {
            key: 'Permissions-Policy',
            value: 'camera=(), microphone=(), geolocation=()',
          },
        ],
      },
    ];
  },
};

export default nextConfig;

Content Security Policy

Prevent XSS attacks with CSP:

const ContentSecurityPolicy = `
  default-src 'self';
  script-src 'self' 'unsafe-eval' 'unsafe-inline' https://www.googletagmanager.com;
  style-src 'self' 'unsafe-inline';
  img-src 'self' blob: data: https:;
  font-src 'self' data:;
  object-src 'none';
  base-uri 'self';
  form-action 'self';
  frame-ancestors 'none';
  upgrade-insecure-requests;
`;

const nextConfig: NextConfig = {
  async headers() {
    return [
      {
        source: '/:path*',
        headers: [
          {
            key: 'Content-Security-Policy',
            value: ContentSecurityPolicy.replace(/\s{2,}/g, ' ').trim(),
          },
        ],
      },
    ];
  },
};

Header Explanations

Strict-Transport-Security (HSTS)

Forces browsers to use HTTPS:

Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
  • max-age: How long to remember (2 years)
  • includeSubDomains: Apply to all subdomains
  • preload: Include in browser preload list

X-Frame-Options

Prevents clickjacking:

X-Frame-Options: SAMEORIGIN

Options:

  • DENY: Never allow framing
  • SAMEORIGIN: Only allow same-origin framing
  • ALLOW-FROM uri: Allow specific URI

X-Content-Type-Options

Prevents MIME type sniffing:

X-Content-Type-Options: nosniff

X-XSS-Protection

Legacy XSS protection:

X-XSS-Protection: 1; mode=block

Referrer-Policy

Controls referrer information:

Referrer-Policy: origin-when-cross-origin

Options:

  • no-referrer: Never send referrer
  • origin: Send origin only
  • origin-when-cross-origin: Full URL for same-origin, origin for cross-origin
  • strict-origin-when-cross-origin: Most restrictive

Permissions-Policy

Control browser features:

Permissions-Policy: camera=(), microphone=(), geolocation=()

Disable unused features to reduce attack surface.

CSP Directives

default-src

Fallback for other directives:

default-src 'self';

script-src

Control JavaScript sources:

script-src 'self' 'unsafe-inline' 'unsafe-eval' https://trusted.com;

Note: Avoid unsafe-inline and unsafe-eval in production.

style-src

Control CSS sources:

style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;

img-src

Control image sources:

img-src 'self' data: https: blob:;

connect-src

Control fetch, XHR, WebSocket:

connect-src 'self' https://api.yourapp.com;

font-src

Control font sources:

font-src 'self' https://fonts.gstatic.com;

CSP with Nonce

For inline scripts with Next.js:

// app/[locale]/layout.tsx
import { headers } from 'next/headers';
import Script from 'next/script';

export default async function RootLayout({ children }) {
  const nonce = headers().get('x-nonce');

  return (
    <html>
      <head>
        <Script
          id="inline-script"
          nonce={nonce}
          dangerouslySetInnerHTML={{
            __html: `console.log('Inline script with nonce');`,
          }}
        />
      </head>
      <body>{children}</body>
    </html>
  );
}

Middleware Headers

Add headers in middleware:

// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

export function middleware(request: NextRequest) {
  const response = NextResponse.next();

  response.headers.set('X-Custom-Header', 'value');
  response.headers.set(
    'Strict-Transport-Security',
    'max-age=63072000; includeSubDomains; preload'
  );

  return response;
}

Testing Headers

Using curl

curl -I https://yoursite.com

Using Security Headers Scanner

Check your headers at:

Using Browser DevTools

  1. Open DevTools (F12)
  2. Go to Network tab
  3. Reload page
  4. Click on the main document
  5. Check Response Headers

CORS Headers

Configure CORS for API routes:

// app/api/public/route.ts
import { NextResponse } from 'next/server';

export async function GET(request: Request) {
  const data = { message: 'Public API' };

  return NextResponse.json(data, {
    headers: {
      'Access-Control-Allow-Origin': '*',
      'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
      'Access-Control-Allow-Headers': 'Content-Type, Authorization',
    },
  });
}

export async function OPTIONS(request: Request) {
  return new NextResponse(null, {
    status: 200,
    headers: {
      'Access-Control-Allow-Origin': '*',
      'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
      'Access-Control-Allow-Headers': 'Content-Type, Authorization',
      'Access-Control-Max-Age': '86400',
    },
  });
}

Production Headers

Complete production configuration:

const securityHeaders = [
  {
    key: 'Strict-Transport-Security',
    value: 'max-age=63072000; includeSubDomains; preload',
  },
  {
    key: 'X-Frame-Options',
    value: 'SAMEORIGIN',
  },
  {
    key: 'X-Content-Type-Options',
    value: 'nosniff',
  },
  {
    key: 'X-XSS-Protection',
    value: '1; mode=block',
  },
  {
    key: 'Referrer-Policy',
    value: 'strict-origin-when-cross-origin',
  },
  {
    key: 'Permissions-Policy',
    value: 'camera=(), microphone=(), geolocation=(), interest-cohort=()',
  },
  {
    key: 'Content-Security-Policy',
    value: ContentSecurityPolicy.replace(/\s{2,}/g, ' ').trim(),
  },
];

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

Best Practices

  1. Enable HSTS: Force HTTPS
  2. Use strict CSP: Prevent XSS attacks
  3. Disable unused features: Reduce attack surface
  4. Test thoroughly: Check all pages work with headers
  5. Monitor violations: Log CSP violations
  6. Regular updates: Keep headers up to date
  7. Use nonces: For inline scripts instead of unsafe-inline

CSP Violation Reporting

Report CSP violations:

const ContentSecurityPolicy = `
  default-src 'self';
  script-src 'self';
  report-uri /api/csp-report;
  report-to csp-endpoint;
`;
// app/api/csp-report/route.ts
export async function POST(request: Request) {
  const report = await request.json();

  console.log('CSP Violation:', report);

  // Send to error tracking service
  // Sentry.captureMessage('CSP Violation', { extra: report });

  return new NextResponse(null, { status: 204 });
}

Next Steps

Security Headers