HTTPS
07 / 07

Security Headers & HTTPS Best Practices

Security Headers & HTTPS Best Practices

Essential Security Headers

Header                          | Effect
--------------------------------|----------------------------------------------
Strict-Transport-Security       | Force HTTPS, tell browser to always use HTTPS
Content-Security-Policy         | Control what resources the page can load
X-Content-Type-Options          | Prevent MIME sniffing
X-Frame-Options                 | Prevent clickjacking (mostly replaced by CSP)
Referrer-Policy                 | Control how much referrer info is sent
Permissions-Policy              | Control browser feature access (camera, GPS)
Cross-Origin-Opener-Policy      | Isolate browsing context (enable SharedArrayBuffer)

HSTS (HTTP Strict Transport Security)

HSTS tells browsers to always use HTTPS for the domain, even if the user types http://. After first visit, the browser refuses plain HTTP connections.

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

max-age=31536000    — remember for 1 year (minimum for preload list)
includeSubDomains   — apply to all subdomains
preload             — submit to browser preload lists (hardcoded HTTPS before first visit)

To join the preload list: https://hstspreload.org
Warning: preload is difficult to undo — only add when all subdomains support HTTPS

Content Security Policy (CSP)

Content-Security-Policy: default-src 'self';
  script-src 'self' https://cdn.jsdelivr.net;
  style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;
  img-src 'self' data: https:;
  font-src 'self' https://fonts.gstatic.com;
  connect-src 'self' https://api.myapp.com;
  frame-ancestors 'none';
  base-uri 'self';
  form-action 'self';
  upgrade-insecure-requests;
  report-uri /csp-report

Directives:
  default-src   — fallback for all other directives
  script-src    — JavaScript sources
  style-src     — CSS sources
  img-src       — Image sources
  connect-src   — fetch/XHR/WebSocket destinations
  frame-ancestors — who can embed this page (replaces X-Frame-Options)
  upgrade-insecure-requests — auto-upgrade http: resources to https:
// Test CSP without enforcing — use Report-Only first
// Content-Security-Policy-Report-Only: <policy>; report-uri /csp-report

// CSP report endpoint
app.post('/csp-report', express.json({ type: 'application/csp-report' }), (req, res) => {
  console.log('CSP Violation:', req.body['csp-report']);
  res.sendStatus(204);
});

Setting Security Headers

// Next.js — next.config.js
const securityHeaders = [
  { key: 'X-DNS-Prefetch-Control', value: 'on' },
  { key: 'Strict-Transport-Security', value: 'max-age=63072000; includeSubDomains; preload' },
  { key: 'X-Content-Type-Options', value: 'nosniff' },
  { key: 'X-Frame-Options', value: 'SAMEORIGIN' },
  { key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },
  { key: 'Permissions-Policy', value: 'camera=(), microphone=(), geolocation=()' },
  {
    key: 'Content-Security-Policy',
    value: "default-src 'self'; script-src 'self' 'unsafe-eval' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src * blob: data:; font-src 'self'",
  },
];

module.exports = {
  async headers() {
    return [{ source: '/(.*)', headers: securityHeaders }];
  },
};
# Check headers with curl
curl -I https://example.com

# Test with securityheaders.com (automated scan)
# Test with Mozilla Observatory: observatory.mozilla.org

# Check specific header
curl -sI https://example.com | grep -i "strict-transport"

Mixed Content

Mixed content occurs when an HTTPS page loads resources (images, scripts, styles) over HTTP. Browsers block mixed content — active content (scripts) always, passive content (images) with warnings.

<!-- Bad: HTTP resource on HTTPS page -->
<script src="http://cdn.example.com/app.js"></script>

<!-- Fix 1: Use HTTPS URL -->
<script src="https://cdn.example.com/app.js"></script>

<!-- Fix 2: Protocol-relative URL (inherits page protocol) -->
<script src="//cdn.example.com/app.js"></script>

<!-- Fix 3: CSP upgrade-insecure-requests header — auto-upgrades all http: to https: -->
<!-- Content-Security-Policy: upgrade-insecure-requests -->

HTTPS Redirect Patterns

// Next.js middleware — redirect HTTP to HTTPS
import { NextResponse } from 'next/server';
export function middleware(request) {
  if (request.headers.get('x-forwarded-proto') !== 'https' && process.env.NODE_ENV === 'production') {
    return NextResponse.redirect(`https://${request.headers.get('host')}${request.nextUrl.pathname}`, 301);
  }
  return NextResponse.next();
}
# Nginx redirect
server {
    listen 80;
    server_name example.com www.example.com;
    return 301 https://example.com$request_uri;
}

# Verify redirect chain (should be single redirect, not chain)
curl -IL http://example.com

OCSP Stapling

OCSP (Online Certificate Status Protocol) lets clients check if a certificate is revoked. OCSP Stapling has the server pre-fetch the OCSP response and include it in the TLS handshake — faster and more private than client fetching it separately.

# Nginx OCSP Stapling config
ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem;
resolver 1.1.1.1 8.8.8.8 valid=300s;

# Verify stapling is working
openssl s_client -connect example.com:443 -servername example.com -status 2>/dev/null \
  | grep -A 17 'OCSP response:'

Keep your own version of these notes — editable, searchable, and organised by your stack.

Start free