Remediation Guides

How to Set X-Content-Type-Options: nosniff (Missing Header Fix)

26 September 2026 9 min read

The finding “Missing ‘X-Content-Type-Options’ Header” means your web server sends responses without X-Content-Type-Options: nosniff, so browsers may guess (sniff) a response’s type instead of trusting its Content-Type. Fix it by adding the header with the value nosniff to every response, including error pages, and serving each file with the correct Content-Type.

What the scanner is actually detecting

Tenable Web App Scanning reports this as plugin 112529, Missing ‘X-Content-Type-Options’ Header, in the HTTP Security Header family, rated Low. Its solution is one sentence: configure the web server to include an X-Content-Type-Options header with a value of nosniff. OWASP ZAP raises the same condition as passive alert 10021, X-Content-Type-Options Header Missing, also Low (CWE-693, WASC-15). ZAP fires when the header is absent or not set to nosniff, and it deliberately includes error responses such as 401, 403 and 500. At the High alert threshold ZAP stops reporting client and server error responses.

Both checks are passive: the scanner reads response headers from pages it has already requested. Nothing is exploited. Burp Scanner has a related informational issue, Content type is not specified, for responses that carry a body but no Content-Type at all. It often appears on the same endpoints and shares a root cause, so fix both together.

How serious is a missing nosniff header?

MIME sniffing is a browser inspecting a response body to decide what it is, instead of relying on the declared Content-Type. The attack it enables looks like this: an application lets users upload a file or returns user input in a response, serves it as text/plain or with no type at all, and a browser that sniffs renders it as HTML or runs it as script. An upload feature then becomes a stored cross-site scripting path.

With nosniff, the Fetch standard and MDN describe two effects:

  • Request blocking: a response loaded as a script (including workers) is blocked unless its type is a JavaScript MIME type, and a response loaded as a stylesheet is blocked unless its type is text/css.
  • No sniffing for everything else: the browser uses the declared Content-Type as is. In particular, it will not treat a response as text/html when the Content-Type is absent or names a non-HTML type.

Stated honestly: both Tenable and ZAP rate this Low, and it is a defense-in-depth control, not a vulnerability by itself. It matters most on applications that serve user-controlled content (uploads, attachments, API responses that echo input). On a static brochure site the practical risk is small. It is still worth fixing, because the change is one line per server and it takes the finding off every report.

How to confirm it on the host

Use the exact URLs listed in the finding. Request each one with GET and print the two headers that matter:

curl -s -D - -o /dev/null https://app.example.com/ | grep -iE '^(x-content-type-options|content-type):'

Then check the places where the header usually goes missing: an error page, a redirect, and your script and stylesheet files. Script and stylesheet files must return a JavaScript MIME type and text/css, or browsers will block them once nosniff is on:

for u in /does-not-exist /login /static/app.js /static/site.css; do
  echo "== $u"
  curl -s -D - -o /dev/null "https://app.example.com$u" | grep -iE '^(x-content-type-options|content-type):'
done
curl -s -D - -o /dev/null http://app.example.com/ | grep -iE '^(x-content-type-options|content-type):'

On Windows, call curl.exe explicitly (in Windows PowerShell 5.1, curl is an alias for Invoke-WebRequest) and filter with Select-String:

curl.exe -s -D - -o NUL https://app.example.com/does-not-exist | Select-String -Pattern '^(x-content-type-options|content-type):'

No X-Content-Type-Options line, or a value other than nosniff, confirms the finding. If the scan targeted an IP address, repeat the test against the IP (add -k, since the certificate will not match), because it may reach a different virtual host.

How to fix it: set X-Content-Type-Options nosniff per web server

The only valid value is nosniff. Set it on every response, not only on successful HTML pages, and at the same time make sure every response with a body declares a correct Content-Type (with a charset for text types).

nginx

Add the header in the server block (or the http block to cover every site):

add_header X-Content-Type-Options "nosniff" always;
charset utf-8;

Without always (nginx 1.7.5 and later), nginx adds the header only to a fixed list of success and redirect codes, so 4xx and 5xx responses go out without it. The charset directive appends the charset to the Content-Type of text/html and the other types listed in charset_types. Keep include mime.types; in the http block so static files get proper types.

Watch the inheritance rule: add_header directives are inherited from the previous level only if the current level defines none. Any location with its own add_header (for Cache-Control, for example) silently drops the server-level header. Repeat the line there, keep your headers in an included snippet, or on nginx 1.29.3 and later use add_header_inherit merge;. If a proxied application already sends the header, add proxy_hide_header X-Content-Type-Options; to that location so clients get one copy.

sudo nginx -t && sudo systemctl reload nginx

Apache HTTP Server

The Header directive needs mod_headers (on Debian and Ubuntu: sudo a2enmod headers). In the virtual host or server config:

Header onsuccess unset X-Content-Type-Options
Header always set X-Content-Type-Options "nosniff"
AddDefaultCharset utf-8

The always condition adds the header to non-2xx responses and keeps it across internal redirects such as ErrorDocument handlers; the default onsuccess table covers 2xx responses only. The unset line follows the Apache documentation’s pattern for avoiding a duplicate when a backend (PHP-FPM, a proxied app) already sets the header. AddDefaultCharset applies only to text/plain and text/html responses.

sudo apachectl configtest
sudo systemctl reload apache2    # httpd on RHEL-family systems

IIS

Add a custom header in the site’s web.config (or in applicationHost.config for the whole server). The remove line prevents a duplicate-entry error if a parent level already defines it:

<configuration>
  <system.webServer>
    <httpProtocol>
      <customHeaders>
        <remove name="X-Content-Type-Options" />
        <add name="X-Content-Type-Options" value="nosniff" />
      </customHeaders>
    </httpProtocol>
  </system.webServer>
</configuration>

Or from an elevated prompt with appcmd:

%windir%system32inetsrvappcmd.exe set config "Default Web Site" -section:system.webServer/httpProtocol /+"customHeaders.[name='X-Content-Type-Options',value='nosniff']"

In IIS Manager: select the site, open HTTP Response Headers, click Add, enter the name and value. Microsoft documents that custom headers are returned in every response. If a static file extension is served with the wrong type, correct it under MIME Types (the <staticContent><mimeMap> element).

Apache Tomcat

The built-in org.apache.catalina.filters.HttpHeaderSecurityFilter sends nosniff on every response by default (blockContentTypeSniffingEnabled is true). It also enables HSTS on HTTPS requests and X-Frame-Options, so switch those off if another layer manages them. Add to WEB-INF/web.xml or $CATALINA_BASE/conf/web.xml:

<filter>
  <filter-name>httpHeaderSecurity</filter-name>
  <filter-class>org.apache.catalina.filters.HttpHeaderSecurityFilter</filter-class>
  <async-supported>true</async-supported>
  <init-param><param-name>hstsEnabled</param-name><param-value>false</param-value></init-param>
  <init-param><param-name>antiClickJackingEnabled</param-name><param-value>false</param-value></init-param>
</filter>
<filter-mapping>
  <filter-name>httpHeaderSecurity</filter-name>
  <url-pattern>/*</url-pattern>
</filter-mapping>

Restart Tomcat or redeploy the application.

CDNs, load balancers and WAFs

The scanner sees the headers of whatever answers first. If a CDN, reverse proxy or WAF sits in front, either set the header there or confirm the origin’s header passes through unchanged. Edge devices can add or strip response headers, which is one of several ways a WAF changes what your web scanner reports.

How to verify the fix and rescan

Rerun the curl loop above. Every URL, including the 404 and the redirect, should show exactly one X-Content-Type-Options: nosniff line, and each script and stylesheet should show a JavaScript MIME type or text/css. Then open the application in a browser with developer tools on and click through the main pages: a script or stylesheet blocked for a MIME type mismatch appears as a console error, which tells you exactly which file needs a corrected type.

Finally, rescan the same targets. In Tenable WAS, rerun the scan configuration that raised plugin 112529. In ZAP, the alert is passive, so the responses must be requested again (spider or proxy the site) before it clears. Compare the new URL list against the old one, not just the count.

What can break and how to roll back

  • Scripts with the wrong type: JavaScript served as text/plain, text/html or application/octet-stream (common with dynamically generated scripts and unmapped extensions) is blocked. Fix the type mapping rather than removing the header.
  • Stylesheets not served as text/css: blocked, and the page renders unstyled.
  • JSONP endpoints: a response typed application/json and loaded through a script tag is not a JavaScript MIME type, so it is blocked.
  • nginx inheritance: adding add_header to a location that previously inherited server-level headers drops all of them there, including HSTS and CSP.

Rollback is immediate because only a response header changes. Back up the config first; to revert, remove the lines, run the config test and reload (on IIS, restore web.config or delete the entry under HTTP Response Headers; on Tomcat, remove the filter and restart).

Common false positive reasons

  • Success-only headers: without always in nginx or Apache, error pages lack the header, so the scanner is right about those responses. ZAP at High threshold ignores them.
  • Different virtual host: scanning by IP often reaches a default site you did not configure.
  • Wrong layer: the header is set on the CDN but the scanner hit the origin directly, or the reverse.
  • Misspelled value: anything other than nosniff (for example “no-sniff”) is ignored by browsers, so this is a true positive.
  • Stale results: the scan ran before the reload, or a cache served old headers.

FAQ

Does nosniff replace a correct Content-Type?

No, it depends on one. The header tells the browser to trust the declared type, so a wrong type now causes blocked files instead of a silent guess. Fix types first on sites with legacy static content.

Are there other values besides nosniff?

No. The Fetch standard defines nosniff (case-insensitive) as the only value, and browsers check only the first value if the header appears more than once.

Do images, downloads and API responses need the header?

Scanners check every response, and setting it globally is simpler than tracking which paths are HTML. It is harmless on responses that already carry the right type.

Can I set it in an HTML meta tag?

No. It is a response header; set it in the web server, proxy or application.

Tracking this finding across many hosts

Missing header findings tend to appear on dozens of virtual hosts at once. SITEY, a self-hosted vulnerability management platform, imports findings from 16 scanners and merges duplicates within each scanner, not across scanners, so the same missing header reported by two tools stays as two records. Per-finding retest is available for Nessus, Acunetix and Burp findings; for other scanners, rerun the scan in that tool to confirm closure.

Sources

SITEY closes the loop, not just the report.Discover, validate, fix and verify in your own infrastructure.

See pricing