IdeaValue

How to Set Up HSTS: HTTP to HTTPS Redirects and Strict-Transport-Security

By the IDEAVALU team · Updated

HSTS tells the browser to connect to your site only over HTTPS from now on. Redirecting http to https is not enough on its own, because that first http request still travels unencrypted.

Why a redirect is not enough

When someone opens http://, the server answers with a redirect to https://. But that first request and the redirect are not encrypted, so on public Wi-Fi an attacker can rewrite them and send the visitor to a fake page (SSL stripping).

A browser that has received HSTS connects over https:// from the start for the length of max-age, even when a link or typed address says http://. The risky first http request never happens.

Check it yourself

Run these commands in a terminal. You are set up if the first shows Strict-Transport-Security and the second shows a Location starting with https://.

Is HSTS set?

curl -sI https://example.com | grep -i strict-transport-security

Does http redirect to https?

curl -sI http://example.com | grep -i location

Choosing max-age, includeSubDomains and preload

Set max-age to at least six months (15552000 seconds); two years (63072000 seconds) is common. includeSubDomains makes HTTPS mandatory on every subdomain too, so add it only after confirming none of them lack HTTPS.

Adding preload and submitting to hstspreload.org enforces HTTPS even on a visitor's first visit. Removal takes a long time, so do this only if every subdomain will stay on HTTPS.

How to fix

HTTP to HTTPS redirect

Redirect requests for http:// to https://.

Vercel, Netlify and Cloudflare Pages redirect automatically. If yours does not, check that the domain's DNS does not point to another server.

nginx

server {
  listen 80;
  server_name example.com;
  return 301 https://$host$request_uri;
}

HSTS (HTTPS enforcement)

With Strict-Transport-Security, browsers always connect over https from the next visit on.

Next.js

// next.config.mjs
const securityHeaders = [
  { key: 'Strict-Transport-Security', value: 'max-age=63072000; includeSubDomains' },
  { key: 'X-Frame-Options', value: 'SAMEORIGIN' },
  { key: 'X-Content-Type-Options', value: 'nosniff' },
  { key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },
  { key: 'Permissions-Policy', value: 'camera=(), microphone=(), geolocation=()' },
]

export default {
  async headers() {
    return [{ source: '/:path*', headers: securityHeaders }]
  },
}

Static sites, Vite and others on Vercel

// vercel.json (for non-Next.js sites on Vercel)
{
  "headers": [
    {
      "source": "/(.*)",
      "headers": [
        { "key": "Strict-Transport-Security", "value": "max-age=63072000; includeSubDomains" },
        { "key": "X-Frame-Options", "value": "SAMEORIGIN" },
        { "key": "X-Content-Type-Options", "value": "nosniff" },
        { "key": "Referrer-Policy", "value": "strict-origin-when-cross-origin" },
        { "key": "Permissions-Policy", "value": "camera=(), microphone=(), geolocation=()" }
      ]
    }
  ]
}

Netlify and Cloudflare Pages

# public/_headers (Netlify, Cloudflare Pages)
/*
  Strict-Transport-Security: max-age=63072000; includeSubDomains
  X-Frame-Options: SAMEORIGIN
  X-Content-Type-Options: nosniff
  Referrer-Policy: strict-origin-when-cross-origin
  Permissions-Policy: camera=(), microphone=(), geolocation=()

One config fixes HSTS, clickjacking, nosniff, Referrer-Policy and Permissions-Policy together. If your app uses the camera or location, change that feature to self in Permissions-Policy.

FAQ

Can I go back to HTTP after enabling HSTS?
Sending max-age=0 turns it off. But browsers that already visited keep enforcing HTTPS for the old max-age until they receive that response.
How to Set Up HSTS on Next.js, Vercel and Netlify | IDEAVALU