dotvitals

The response has a body but no Content-Type

MediumConfirmedhttp.headers.content-type-missing

What this check looks for

The server sent content without saying what kind of content it is, so each browser guesses. Guessing wrong renders the page as raw text — or runs a file as code.

Why it matters

This is a rendering bug and a security hole at the same time. Content sniffing is precisely the behaviour X-Content-Type-Options exists to switch off, and with no Content-Type at all there is nothing for that header to protect.

When the check passes, your report says: “The response declares the kind of content it carries”.

What it costs your score

When this check fails it removes 10 points from your HTTP score, before the status, confidence and repeat multipliers are applied. HTTP carries a weight of 7 in the overall score.

It shares the http.content-type family ceiling of 12 points: however many findings that family produces, together they cannot remove more than that from HTTP. One underlying problem showing up in several places is still one problem.

Severity
medium
Default confidence
confirmed
Status when triggered
fail
Deduction
10 points
Family cap
http.content-type · 12
Category
HTTP
Module
Http headers
Fix owned by
user
In the ruleset since
2026.09

How the whole score is calculated

How to fix it

Declare a Content-Type on every response that has a body.

Without one the browser guesses the type, which breaks rendering and re-opens the sniffing attack surface.

  1. Set it where the response is built. An application that writes the body directly must set the header directly; a web server serving files from disk derives it from the extension.

  2. Include the charset for any text type: text/html; charset=utf-8.

  3. Use the accurate type rather than a catch-all — application/json, image/svg+xml, application/pdf. application/octet-stream is correct only for a genuine download.

  4. For anything user-uploaded, serve it with a type you determined server-side, never one the uploader supplied.

  5. Add X-Content-Type-Options: nosniff once the types are correct, so browsers stop second-guessing them.

How to confirm it worked

  • curl -sSI https://‹host›/ | grep -i '^content-type:' — expect a type, with a charset for text

The configuration to publish
Content-Type: text/html; charset=utf-8
X-Content-Type-Options: nosniff

A named slot like ‹domain› — and the braces left in the configuration below — is filled in with your own values when this rule appears on a report.

Remediation by platform

nginx
# Adds charset=utf-8 to the types listed in charset_types (text/html is always included):
charset utf-8;

# Only affects files nginx serves itself, never a proxied response:
include mime.types;
default_type application/octet-stream;
Apache
AddDefaultCharset utf-8

<IfModule mod_headers.c>
	Header always set Content-Type "text/html; charset=utf-8" "expr=%{CONTENT_TYPE} =~ m#^text/html$#"
</IfModule>
Caddy
header Content-Type "text/html; charset=utf-8"
Page HTML
<meta charset="utf-8">
  • For a response from an application behind proxy_pass, nginx forwards the upstream's Content-Type untouched — the fix belongs in the application. default_type applies only to files nginx serves whose extension it cannot map.

  • Shown because the response identified Apache. Put it in the virtual host or server configuration rather than .htaccess: .htaccess is re-read on every request, is often disabled by AllowOverride None, and runs too late to influence a response the virtual host generates itself. AddDefaultCharset appends the charset to text/plain and text/html responses that do not already carry one, and is the smaller of the two changes; the Header rule is only needed if something later in the chain overwrites it.

  • Caddy's static file server already appends charset=utf-8 to text types it detects, so this is needed mainly in front of a reverse-proxied application. Scope it with a matcher so it cannot mislabel images or downloads.

  • A fallback, not the fix: the HTTP header wins over the meta tag whenever both are present, and the tag has to appear within the first 1024 bytes of the document to be honoured at all.

Technical detail

The response from ‹origin› had a body and no Content-Type header. ‹detail›

RFC 9110 §8.3 says a sender that generates a message containing content **should** generate a Content-Type, and that a recipient receiving content without one may inspect the content to determine its type — which is the specification describing sniffing rather than endorsing it. The concrete consequences:

  • The browser's guess is not a standard. Different engines reach different conclusions about the same bytes, so the same URL can render as a page in one browser and download as a file in another.
  • Sniffing is the attack surface. A file uploaded as an image but containing HTML can be sniffed as HTML and executed as a page on your own origin. X-Content-Type-Options: nosniff closes that — by making the browser trust the declared type absolutely, which requires there to *be* a declared type.
  • Character encoding is unknowable, so a document's text is decoded by heuristic as well.

A missing Content-Type almost always comes from an application writing the body directly rather than from the web server, which sets one from the file extension. Detected server: ‹detected server›.

Standards and references

Test this on your domain

Run the check that produces this finding, on its own, against any domain.

Open the http headers checkerBuild the fix

Other http headers checks