How to Fix Missing Security Headers Flagged in a Vulnerability Scan
A vulnerability scan or penetration test that reports missing HTTP security headers is one of the most common findings a small business site gets. The headers tell browsers to use HTTPS only, block framing by other sites, stop file-type guessing and limit where scripts may load from. Each one is a single line of configuration, but they are easy to set in the wrong place, with an invalid value, or on only some responses, which is why the finding often comes back after a first attempt to fix it. This guide shows how to check your security headers online and from the command line, which headers matter and what valid values look like, and how to add them at server, CDN or CMS level.
- What a missing security headers finding means
- Which security headers matter, and their valid values
- How to check security headers on your site
- Why a security header is not valid
- Adding the headers at server, CDN or CMS level
- A worked example
- The fix, step by step
- Edge cases worth knowing
- Keeping the headers in place
- Questions and answers
What a missing security headers finding means
HTTP security headers are instructions your server sends with every response, telling the browser how to handle the page: always use HTTPS, which scripts may run, whether the page may be framed by another site, and how much of the URL to share when a visitor clicks away. They cost nothing to add and protect against whole classes of attack, such as clickjacking, content-type confusion and some cross-site scripting.
A vulnerability scanner or penetration test reports a finding such as Missing HTTP security headers when one or more of these is absent, or when a header is present but has a value the browser will ignore, often reported as security header is not valid. On its own, a missing header is usually rated low or informational, because it does not create a hole by itself. It removes a layer of protection that would limit the damage if something else went wrong.
That rating matters for how you respond. You do not need to take the site offline, but you should fix it, because the fix is quick, and because insurers, clients and compliance checklists increasingly expect to see these headers.
Which security headers matter, and their valid values
These are the headers most scanners check for, with a safe starting value for a typical small business site.
| Header | Safe starting value | What it does |
|---|---|---|
Strict-Transport-Security | max-age=31536000; includeSubDomains | Tells browsers to use HTTPS only for this host for a year |
Content-Security-Policy | Built for your site; start in report-only mode | Limits where scripts, styles, frames and other resources may load from |
X-Content-Type-Options | nosniff | Stops the browser guessing file types, so a text file is not run as a script |
X-Frame-Options | SAMEORIGIN | Stops other sites framing your pages for clickjacking |
Referrer-Policy | strict-origin-when-cross-origin | Sends only your domain, not the full URL, to other sites |
Permissions-Policy | camera=(), microphone=(), geolocation=() | Switches off browser features your site does not use |
Two older headers still appear in some scan reports. X-XSS-Protection controlled a filter that modern browsers have removed; current advice is to omit it or send 0, so if a scanner asks for 1; mode=block, note that the recommendation is outdated. Feature-Policy was replaced by Permissions-Policy, which uses a different syntax.
max-age. Start with max-age=300, check every subdomain works over HTTPS, then raise it. Do not add preload until you are sure. See fixing HSTS errors.How to check security headers on your site
The quickest check is from the command line, which shows exactly what the server sends:
curl -sI https://www.example.com/ | grep -iE 'strict-transport|content-security|x-content-type|x-frame|referrer-policy|permissions-policy'
Any header missing from the output is missing from the response. Run the same check on a few different URL types, because headers are often set for some responses and not others:
for u in / /contact/ /wp-login.php /images/logo.png /no-such-page; do
echo "== $u"; curl -sI "https://www.example.com$u" | grep -icE 'strict-transport|x-frame|x-content-type'
done
A count that drops for the 404 or the image tells you the headers are being added by the application rather than the server, or only on successful responses.
Checking security headers online
If you would rather not use a terminal, an online checker fetches the page and grades each header. Our security audit lists missing and invalid headers with the value it found, and Mozilla's HTTP Observatory is a well-known free alternative. In the browser, open DevTools, go to the Network tab, reload, click the first document request and read the Response Headers section.
Why a security header is not valid
A header that is present but wrongly written is treated by the browser as if it were missing. These are the mistakes scanners flag most often:
| Problem | Example | Correct form |
|---|---|---|
HSTS with no max-age | includeSubDomains | max-age=31536000; includeSubDomains |
| HSTS sent over plain HTTP | Header on the http:// response | Browsers ignore it there; send it on HTTPS responses |
| Obsolete framing value | X-Frame-Options: ALLOW-FROM https://example.org | Use CSP frame-ancestors instead |
| CSP keywords without quotes | script-src self | script-src 'self' |
| Old Feature-Policy syntax | Permissions-Policy: camera 'none' | Permissions-Policy: camera=() |
| Duplicate headers | Two X-Frame-Options with different values | Set it in one place only |
| Unknown value | Referrer-Policy: strict | One of the defined values, such as no-referrer |
Duplicates are the hardest to spot. They happen when the application, a plugin, the web server and the CDN each add the same header. Check with curl -sI and count the lines for each header name; there should be one.
A Content Security Policy needs more care than the others, because a strict one can block your own scripts, analytics or payment forms. Roll it out with Content-Security-Policy-Report-Only first, as the CSP report-only guide explains.
Adding the headers at server, CDN or CMS level
Add the headers in one place, as close to the edge as you can, so every response gets them. On nginx, in the server block for HTTPS:
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
The always flag adds the header to error responses too. Note that a location block with any add_header of its own stops inheriting these, so repeat them there or move them into an included file. More in the nginx security headers guide.
On Apache with mod_headers enabled, in the virtual host or .htaccess:
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
Header always set X-Content-Type-Options "nosniff"
Header always set X-Frame-Options "SAMEORIGIN"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Header always set Permissions-Policy "camera=(), microphone=(), geolocation=()"
Using set rather than add replaces any copy the application already sent, which avoids duplicates.
Other platforms
- Cloudflare: HSTS is under SSL/TLS, Edge Certificates. Other headers can be set with a response header transform rule. See the Cloudflare guide.
- Plesk: add the nginx or Apache lines under the domain's Apache and nginx Settings; see the Plesk guide.
- WordPress: server-level headers are better than a plugin, but a headers plugin works on shared hosting. See the WordPress guide.
- Windows IIS: use
customHeadersinweb.config, covered in the IIS guide.
A worked example
A scan of www.example.com, an Example Bakery site on nginx behind Cloudflare, reported four findings: missing HSTS, missing X-Frame-Options, missing Content-Security-Policy and X-XSS-Protection not set.
- curl showed HSTS was present on the home page but missing on
/shop/. The shop'slocationblock had its ownadd_headerfor caching, which stopped inheritance. Moving the security headers into an included file and including it in both blocks fixed it. - X-Frame-Options was added at server level with
always. - For CSP, the team sent a report-only policy for two weeks, collected the reports, added the payment provider's domain to
frame-srcandscript-src, then switched to enforcing. - X-XSS-Protection was left out on purpose, with a note in the response to the scanner explaining that the filter has been removed from modern browsers.
The rescan showed all required headers present with valid values on every URL type tested, including the 404 page.
The fix, step by step
Step 1
Record exactly what was flagged
Copy the scanner or pentest finding word for word, with the URLs tested and the headers named as missing or not valid. Note whether it tested HTTP or HTTPS, and whether it covered images, error pages or other subdomains. This tells you where the gap is, and it gives you something to compare against when you rescan. Keep the original report with the date, so the fix can be shown to whoever asked for it.
Step 2
Check the live headers with curl
Run curl -sI on the home page and on a few other URL types, such as a deep page, an image, a login page and a 404, and grep for the security header names. Note which are missing on which responses. If they appear on some and not others, the cause is usually inheritance or error responses, rather than the header never being set at all.
Step 3
Find what already sets headers
Look at every layer that could add headers: the application or CMS, plugins, the web server configuration, the control panel and the CDN. Search the configuration for add_header on nginx, Header set on Apache, or customHeaders on IIS. Pick one layer to own the security headers, ideally the web server or CDN, and plan to remove duplicates from the others. Write down which layer you chose, so the next person who edits the site knows where the headers live.
Step 4
Add the simple headers first
Add X-Content-Type-Options: nosniff, X-Frame-Options: SAMEORIGIN, Referrer-Policy: strict-origin-when-cross-origin and a Permissions-Policy that switches off features you do not use. On nginx use always, on Apache use Header always set. Test the configuration first with nginx -t or apachectl configtest, so a typing mistake does not take the site down. Reload the server, then run curl again and confirm each header appears exactly once on every URL type you checked, including the 404 page.
Step 5
Add HSTS carefully
Confirm every page and subdomain works over HTTPS, then add Strict-Transport-Security with a short max-age such as 300 on HTTPS responses. Check the site for a few days, then raise it to 31536000 and add includeSubDomains once each subdomain is confirmed. Leave preload until you are certain. Confirm with curl that the header appears on the HTTPS response with exactly the value you set.
Step 6
Roll out CSP in report-only mode
Write a starting policy listing the domains your site loads scripts, styles, fonts, images and frames from. Send it as Content-Security-Policy-Report-Only with a reporting endpoint, and leave it for one to two weeks. Review the reports, add the legitimate sources, then switch the header name to Content-Security-Policy. Afterwards, test checkout, contact forms, embedded maps and analytics with the browser console open, and look for any blocked resource messages that the reports missed.
Step 7
Fix invalid values and duplicates
Compare each header against its valid syntax: HSTS needs max-age, CSP keywords need single quotes, Permissions-Policy uses the feature=() form, and ALLOW-FROM is obsolete. Count each header in the curl output; any header listed twice needs removing from one layer. Use set rather than add on Apache so the server replaces copies the application sends, and check again with curl after each change until every header appears once.
Step 8
Rescan and document the result
Purge any CDN or page cache, then run the same scanner on the same URLs as the original report. Confirm each finding is closed. For items you chose not to apply, such as X-XSS-Protection, write a short note explaining why, citing current browser behaviour. Keep the before and after reports together, so you can show auditors or clients when and how each finding was fixed.
Edge cases worth knowing
- Headers on redirects. Scanners sometimes test
http://and flag missing headers on the 301 to HTTPS. HSTS is ignored over HTTP anyway; what matters is the HTTPS response. - Static files and CDNs. Images and scripts served from a CDN or storage bucket may not get your server's headers. The CDN must add them.
- Meta tag CSP. A CSP in a
<meta>tag works for most directives but notframe-ancestorsor reporting. Use the header where you can. - Framing you need. If your own pages are embedded elsewhere, use CSP
frame-ancestorswith the allowed domains rather thanX-Frame-Options. - Subdomains and HSTS.
includeSubDomainsapplies to every subdomain. Check old ones such as a mail or staging host serve HTTPS first.
Keeping the headers in place
Headers disappear after server migrations, control panel updates, new CDN rules and theme changes. Make checking them routine:
- Keep the header set in one file under version control, and include it rather than copying it.
- Run the curl loop above after every deploy or hosting change, and monthly otherwise.
- Keep CSP reporting switched on, so you hear about blocked resources before customers do.
- Rescan after each fix and keep the report, so you can show clients or auditors when the issue was closed. Mixed content warnings often follow HSTS changes; see the mixed content guide.
1. How do I fix missing security headers?
You fix missing security headers by adding them to every response at one layer, usually the web server or CDN. On nginx use add_header with the always flag, on Apache use Header always set, on IIS use customHeaders in web.config, and on Cloudflare use response header transform rules. Start with X-Content-Type-Options, X-Frame-Options, Referrer-Policy and Permissions-Policy, add HSTS with a short max-age first, and roll out Content-Security-Policy in report-only mode before you enforce it, then confirm with curl.
2. Is a missing security header a real vulnerability?
A missing security header is a real finding but usually rated low or informational, because it does not open a hole by itself. It removes a layer of defence that limits damage from other problems, such as clickjacking, script injection or content-type confusion. Fix it because the change is quick and cheap, and because clients, insurers and compliance checklists increasingly expect these headers. Do not treat it as an emergency unless the scanner reports another, more serious issue alongside it.
3. How can I check security headers online?
You can check security headers online with a scanner that fetches your page and grades each header, such as our security audit or Mozilla's HTTP Observatory. Enter the full https address and read which headers are missing or invalid. For a quick manual check, open the browser's developer tools, go to the Network tab, reload, select the main document and read its response headers. curl with the -I option shows the same information from a terminal.
4. What does security header is not valid mean?
Security header is not valid means the header is present but its value does not follow the expected syntax, so the browser ignores it. Common causes are HSTS without a max-age, CSP keywords such as self written without single quotes, X-Frame-Options using the obsolete ALLOW-FROM value, Permissions-Policy written in old Feature-Policy syntax, and the same header sent twice with different values. Compare the value against its specification, correct the syntax, and remove any duplicates set by other layers.
5. Which security headers should every website have?
Every website should send Strict-Transport-Security, X-Content-Type-Options, X-Frame-Options or a CSP frame-ancestors rule, Referrer-Policy and Permissions-Policy, plus a Content-Security-Policy suited to the site. The first five can be added with standard values in a few minutes. CSP needs more planning, because it lists the sources your scripts, styles and frames come from, and a wrong policy can break features. X-XSS-Protection is no longer recommended, and modern browsers ignore it, so leave it out unless a policy you must follow insists on a value of 0.
6. Why are my security headers missing on some pages?
Security headers go missing on some pages when they are added by only one part of the stack. On nginx, a location block with its own add_header stops inheriting the server-level headers, and headers without always are dropped from error responses. Images and scripts served from a CDN or storage bucket do not get your server's headers. Pages served from a cache may predate the change. Check several URL types with curl to find which responses lack them.
7. Should I add X-XSS-Protection to fix the scan?
No, you should generally not add X-XSS-Protection with a value of 1 and mode block, even if an older scanner asks for it. The filter it controlled has been removed from modern browsers, and in older ones it could be abused to cause problems of its own. Current guidance is to omit the header or send it with a value of 0, and rely on a Content Security Policy instead. Note your reasoning in the response to the scan.
Our security audit fetches your pages, lists every missing or invalid security header with the value it found, and shows which responses lack them, so you can fix the right layer.
Open Security Audit Fixes โ