All 30 checks Missing HSTS header
Missing HSTS header
Updated
Without a Strict-Transport-Security header, a browser still tries plain HTTP first whenever someone types your domain or follows an old http:// link, and that first unencrypted request is the one an attacker on the same network can tamper with.
HTTP/2 200
content-type: text/html; charset=utf-8
cache-control: public, max-age=0, must-revalidate
x-content-type-options: nosniffStrict-Transport-Security: max-age=31536000; includeSubDomainsThe fix for your stack
Fix for: Next.js / Vercel
// next.config.ts
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
async headers() {
return [
{
source: "/:path*",
headers: [{ key: "Strict-Transport-Security", value: "max-age=31536000; includeSubDomains" }],
},
];
},
};
export default nextConfig;What the browser does with HSTS, and without it
HTTP Strict Transport Security is a single response header. When a browser receives it over a valid HTTPS connection, it records the host and, for the number of seconds in max-age, refuses to talk to that host over plain HTTP. A typed example.com, a bookmark saved years ago as http://example.com, or a link in an old email all get rewritten to https:// inside the browser before any packet leaves the device. Certificate errors on that host also become hard failures the user cannot click through.
Without the header, the browser has no memory of your HTTPS setup. It sends the first request over HTTP, your server answers with a 301 to the HTTPS address, and only then does the encrypted session start. That redirect travels in cleartext. On a hostile network someone can answer in place of your server, keep the visitor on HTTP, and proxy the real site behind the scenes. The technique is old and well documented, and a redirect alone does not stop it.
Who is exposed
The people at risk are visitors on networks they do not control: hotel and airport Wi-Fi, conference networks, shared office connections. Anyone arriving through an http:// link is in the same position, and older sites tend to have plenty of those scattered across directories, printed material, and email signatures.
For an agency, the header is also a visibility problem. Client IT teams, insurers, and procurement questionnaires run header scanners, and a missing HSTS line is one of the first items they list. Handing over a site that fails it invites a support thread you could have avoided with one line of config.
How Skewcheck detects it
Skewcheck reads the response headers of the audited URL and looks for strict-transport-security. If the header is absent, the check fails with high severity and removes 6 points from the audit score, which starts at 100. If the header is present, the check passes. Skewcheck does not grade the max-age value, so choosing a sensible duration is still your job.
To see what Skewcheck sees, request the page yourself and filter the headers. Header names are case-insensitive, so the lowercase form shown by HTTP/2 is the same header.
curl -sI https://example.com | grep -i strict-transport-securityAdding the header on your platform
Set the header on the HTTPS side only. In nginx, the always flag makes it appear on error responses too, not just 200s. In Apache, mod_headers must be enabled (a2enmod headers on Debian-based systems). On Vercel, a headers entry in vercel.json applies to every path that matches the source pattern. In a Next.js app you can return it from the headers function in next.config. On Cloudflare, HSTS is a toggle in the edge certificate settings rather than something you write by hand. Some hosts already send a default HSTS header, so check the live response before adding a second one.
server {
listen 443 ssl;
server_name example.com;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
# ssl_certificate, root, locations, etc.
}<VirtualHost *:443>
ServerName example.com
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
</VirtualHost>{
"headers": [
{
"source": "/(.*)",
"headers": [
{
"key": "Strict-Transport-Security",
"value": "max-age=31536000; includeSubDomains"
}
]
}
]
}import type { NextConfig } from "next";
const nextConfig: NextConfig = {
async headers() {
return [
{
source: "/:path*",
headers: [
{
key: "Strict-Transport-Security",
value: "max-age=31536000; includeSubDomains",
},
],
},
];
},
};
export default nextConfig;Mistakes that bite later
Browsers ignore Strict-Transport-Security when it arrives over plain HTTP, so adding it to the port 80 server block does nothing. In nginx, any add_header inside a location block replaces every add_header inherited from the server block, which quietly drops HSTS on exactly the paths that define their own headers.
includeSubDomains applies the policy to every subdomain, including the forgotten ones: a staging host without a certificate, an old webmail login, an internal dashboard on HTTP. Once a browser has the policy, those hosts stop loading for that visitor until max-age runs out. Audit your DNS first, or start with max-age=300 and raise it to a year once nothing breaks.
The preload directive is a separate commitment. Submitting a domain to the browser preload list builds the policy into browsers themselves, it requires includeSubDomains and a max-age of at least one year, and removal takes months to reach users. Leave preload off until the whole domain has been HTTPS-only for a while.
Before and after
HTTP/2 200
content-type: text/html; charset=utf-8
x-content-type-options: nosniffHTTP/2 200
content-type: text/html; charset=utf-8
x-content-type-options: nosniff
strict-transport-security: max-age=31536000; includeSubDomainsCheck your page
Run all 30 checks on any URL. Free, no account.