ScreenshotNeo

BlogHow-to

MIME Sniffing Test: Check the X-Content-Type-Options Header

Learn how to verify X-Content-Type-Options: nosniff, inspect Content-Type, test scripts and styles, and fix common MIME sniffing errors.

By the ScreenshotNeo team29 September 20268 min read

MIME Sniffing Test: Check the X-Content-Type-Options Header

The expected header is:

X-Content-Type-Options: nosniff

To test a site, inspect the response headers for the exact page or asset you care about. Confirm both X-Content-Type-Options: nosniff and a correct Content-Type. The header is a focused browser defense: it prevents MIME guessing, but it does not prove that a website is secure overall.

What X-Content-Type-Options does

Browsers normally use the server’s Content-Type response header to decide how to handle a resource. Without protection, a browser may inspect the bytes and infer a different type. This behavior is called MIME sniffing.

Setting X-Content-Type-Options: nosniff tells the browser to respect the declared media type instead of trying to reinterpret the content. MDN documents nosniff as the directive used for this purpose. Read the MDN header reference.

Script and stylesheet requests

For a request whose destination is a script, nosniff blocks the response when its declared MIME type is not an expected JavaScript type. For a stylesheet request, the declared type must be text/css. A JavaScript file served as text/plain, for example, can be rejected instead of executed.

Other response types

For other fetch destinations, the browser uses the declared Content-Type without inspecting the body to infer a type. That makes the accuracy of your media-type declaration important. nosniff does not repair an incorrect type; it makes an incorrect type more visible.

Fast manual check with curl

Run a request that prints response headers and discards the body:

curl -sS -D - -o /dev/null https://example.com/

Use -I for a HEAD request when the server supports it:

curl -sSI https://example.com/

Look for these lines:

Content-Type: text/html; charset=UTF-8
X-Content-Type-Options: nosniff

HEAD responses are not always configured exactly like GET responses. For a definitive check, use the first command, which performs GET and shows the headers returned with the actual body.

Follow redirects

If the URL redirects, inspect every hop:

curl -sS -L -D - -o /dev/null https://example.com/

The output can contain several header blocks. The final response is usually the page you render, but an intermediate response may also matter for your deployment and caching rules.

Check a specific asset

Test the JavaScript, CSS, font, image, or API URL itself. A header on the HTML document does not guarantee that static assets have the same header:

curl -sS -D - -o /dev/null https://example.com/assets/app.js
curl -sS -D - -o /dev/null https://example.com/assets/app.css

Check in browser developer tools

  1. Open the page in your browser.
  2. Open Developer Tools and select the Network panel.
  3. Reload the page so the request is captured.
  4. Select the document or asset you want to verify.
  5. In Response Headers, find X-Content-Type-Options.
  6. Confirm its value is exactly nosniff (header names are case-insensitive, but the directive value should be written as shown).
  7. Check Content-Type in the same response.

For a script or stylesheet, also check the browser Console. A MIME mismatch commonly produces a message saying the resource was blocked because its MIME type is not executable or because strict MIME checking is enabled.

Complete checks in Python

The following script follows redirects, prints the final URL, and reports both required headers:

import sys
import requests

url = sys.argv[1] if len(sys.argv) > 1 else "https://example.com/"
response = requests.get(url, allow_redirects=True, timeout=30)

nosniff = response.headers.get("X-Content-Type-Options")
content_type = response.headers.get("Content-Type")

print("status:", response.status_code)
print("final URL:", response.url)
print("Content-Type:", content_type or "(missing)")
print("X-Content-Type-Options:", nosniff or "(missing)")

if nosniff and nosniff.lower() == "nosniff":
    print("PASS: nosniff is present")
else:
    print("FAIL: expected X-Content-Type-Options: nosniff")

Save it as check_headers.py, install the dependency with python -m pip install requests, and run:

python check_headers.py https://example.com/

For production checks, make the script test a list of representative routes and assets instead of relying on the home page alone.

Complete checks in Node.js

Node.js 18 and later includes fetch:

const target = process.argv[2] || 'https://example.com/';

const response = await fetch(target, { redirect: 'follow' });
console.log('status:', response.status);
console.log('final URL:', response.url);
console.log('Content-Type:', response.headers.get('content-type') || '(missing)');
console.log(
  'X-Content-Type-Options:',
  response.headers.get('x-content-type-options') || '(missing)'
);

const value = response.headers.get('x-content-type-options');
if (value && value.toLowerCase() === 'nosniff') {
  console.log('PASS: nosniff is present');
} else {
  console.log('FAIL: expected X-Content-Type-Options: nosniff');
}

Run it with:

node check-headers.mjs https://example.com/

What a correct Content-Type looks like

The appropriate value depends on the resource:

Resource Typical Content-Type What to verify
HTML document text/html; charset=UTF-8 The route returns HTML and is not mislabeled as plain text.
JavaScript text/javascript or another browser-supported JavaScript MIME type The server and CDN preserve the JavaScript type.
CSS text/css The stylesheet is not served as HTML, plain text, or JSON.
JSON API response application/json Error pages are not being returned with a misleading success type.
PNG image image/png The file extension and actual response agree.
PDF application/pdf A proxy or authentication page is not replacing the PDF.

Use the media-type definitions appropriate to your application. MDN’s Content-Type reference explains the role of this header and how nosniff affects MIME interpretation.

How to fix a missing or incorrect header

Apache

Add this directive to the applicable virtual host or .htaccess file:

Header always set X-Content-Type-Options "nosniff"

Make sure the headers module is enabled and reload Apache. Verify the public response afterward; an upstream CDN can still remove or replace headers.

Nginx

add_header X-Content-Type-Options "nosniff" always;

The always parameter applies the header to error responses as well. Put the directive in the server or location block that serves the relevant route, then reload Nginx.

Node.js and Express

import express from 'express';

const app = express();
app.use((req, res, next) => {
  res.setHeader('X-Content-Type-Options', 'nosniff');
  next();
});

Set the header before the response is sent. If you use a security middleware, confirm it is enabled for every route and that later middleware does not overwrite the value.

CDN, object storage, and frameworks

Configure the header at the layer that owns the response. Static files may come from object storage, while HTML and API responses come from an application server. Check both origins and the public CDN URL. Purge or wait for cached objects after changing metadata, then repeat the GET check.

Testing strategy for real sites

  1. Test representative HTML: the home page, an authenticated or application route where applicable, and an error page.
  2. Test executable assets: at least one JavaScript and one CSS file actually loaded by the page.
  3. Test redirects: HTTP to HTTPS, canonical host redirects, and locale redirects.
  4. Test multiple delivery paths: origin, CDN, compressed response, and cache hit if those differ.
  5. Record status and type: a header-only pass is not enough if the response is a login page, an error document, or the wrong media type.

MDN HTTP Observatory can scan website security configuration, including this header. Its FAQ explains that scan history is public and that the service is intended for websites rather than API endpoints. A high score is not a complete security audit. See the HTTP Observatory overview and FAQ.

Or skip the browser setup

If you need a clean visual record of a page after checking its headers, ScreenshotNeo can capture the URL through one request. Its capture flow removes cookie banners, newsletter popups, and chat widgets before the shot. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and each response identifies the page verdict and billing status in headers. It also provides an MCP server for AI agents, with take_screenshot, get_page_info, and capture_pdf tools.

See the ScreenshotNeo API documentation for all options. A basic capture looks like this:

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://example.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://example.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

You get 1,000 screenshots each month free with no card. Paid plans start at $5 for 3,000 shots, and every feature is included on every plan. Create a free ScreenshotNeo account.

Troubleshooting common failures

The header is missing

Cause: the application, reverse proxy, CDN, or static-file server does not set it on that response.

Fix: add the header at the serving layer, include error responses where supported, purge caches, and test the public URL with GET.

The value is not nosniff

Cause: a duplicated header, a templating mistake, or a proxy override.

Fix: search the complete response for every occurrence, remove conflicting values, and standardize on X-Content-Type-Options: nosniff.

A script is blocked after enabling nosniff

Cause: the script is served with an incorrect type, often because a missing asset falls back to an HTML error page.

Fix: open the script URL directly, check its status and Content-Type, and correct the route, static mapping, or CDN metadata.

The HTML page works but the CSS does not

Cause: the stylesheet response is mislabeled, redirected to a login page, or returning an error document.

Fix: inspect the CSS request in Network tools and ensure the final response is successful with Content-Type: text/css.

curl and browser results differ

Cause: user-agent rules, cookies, authentication, geographic routing, or cache variation.

Fix: compare URLs, redirects, request headers, and cookies. Test from the same environment when possible and use browser Network tools for the exact failing request.

Performance, reliability, and cost notes

Header inspection is cheap and fast because it does not require downloading or parsing the full body when you use a HEAD request. Use GET for the final verification because some servers handle HEAD differently. For automated monitoring, keep the URL set small and representative, set connection and total timeouts, retry transient network failures with backoff, and record status, final URL, content type, and header value.

Do not treat a scanner grade as proof of security. The header protects one class of MIME confusion. You still need correct routing, output encoding, access controls, authentication, CSP and other controls appropriate to your application. MDN specifically describes nosniff as defense in depth and notes that Observatory does not cover every security issue.

FAQ

Is nosniff the only valid value?

It is the directive browsers use for this protection. In practice, configure the header as X-Content-Type-Options: nosniff.

Does the header stop all XSS?

No. It limits MIME confusion and can block incorrectly typed scripts or stylesheets. It is one security control among many.

Should API responses include it?

It can be useful for API responses, but verify the API’s actual media types and remember that HTTP Observatory is designed for websites, not a complete API assessment.

Do I need it on every asset?

Apply it consistently to responses you serve, then verify representative HTML, scripts, stylesheets, APIs, redirects, and error pages.

Why check Content-Type too?

nosniff tells the browser to trust the declared type. If that declaration is wrong, the browser may correctly refuse to load the resource.