dotvitals

A text response does not declare its character encoding

LowConfirmedQuick winhttp.headers.charset-missing

What this check looks for

The response says it is text but not which alphabet it is written in, so the browser works it out from the bytes. When it works it out wrongly, accented characters and symbols come out as nonsense.

Why it matters

Names, addresses, prices and quotation marks render as mojibake for some visitors and not others, and the encoding a browser picks can depend on its own language settings — so the page looks correct to you and broken to your customers.

When the check passes, your report says: “The text response declares its character encoding”.

What it costs your score

When this check fails it removes 4 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
low
Default confidence
confirmed
Status when triggered
warn
Deduction
4 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

Append charset=utf-8 to the Content-Type of every text response.

Otherwise the browser sniffs the encoding from a locale-dependent default and some visitors see mojibake.

  1. Send Content-Type: text/html; charset=utf-8 — the header wins over the document's meta tag, so this is the authoritative place to set it.

  2. nginx: charset utf-8; appends it to the types in charset_types. Apache: AddDefaultCharset utf-8.

  3. Do the same for CSS, JavaScript, JSON, plain text and SVG. (application/json is defined as UTF-8 and needs no parameter, but a charset on it is harmless.)

  4. Keep <meta charset="utf-8"> in the first 1024 bytes of the document as a fallback for the file being opened outside HTTP.

  5. Confirm the bytes really are UTF-8 — declaring an encoding the content is not in replaces a guess with a wrong answer.

How to confirm it worked

  • curl -sSI https://‹host›/ | grep -i '^content-type:' — expect charset=utf-8

  • Load the page and check that accented characters and typographic quotes render correctly.

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

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

Content-Type: ‹content type› has no charset parameter.

Without it a browser applies the WHATWG Encoding Standard's sniffing algorithm: it scans the first 1024 bytes for a <meta charset>, and failing that falls back to a locale-dependent default — which is exactly why the same page renders correctly in one visitor's browser and as mojibake in another's.

There is a security history here too, and it is worth stating accurately. Encoding sniffing was once a genuine XSS vector: a page whose encoding a browser could be steered into reading as UTF-7 could have +ADw-script+AD4- interpreted as a script tag, slipping past filters that only look for <. Current browsers no longer support UTF-7 for exactly this reason, so on an up-to-date browser today the realistic consequence is mojibake rather than script execution. Declaring charset=utf-8 removes both the ambiguity and the argument.

The HTTP header takes precedence over the document's <meta charset>, so the header is the fix and the meta tag is the fallback. 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