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.