A text response does not declare its character encoding
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 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.
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.nginx:
charset utf-8;appends it to the types incharset_types. Apache:AddDefaultCharset utf-8.Do the same for CSS, JavaScript, JSON, plain text and SVG. (
application/jsonis defined as UTF-8 and needs no parameter, but a charset on it is harmless.)Keep
<meta charset="utf-8">in the first 1024 bytes of the document as a fallback for the file being opened outside HTTP.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.
Content-Type: text/html; charset=utf-8A 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
# 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;AddDefaultCharset utf-8
<IfModule mod_headers.c>
Header always set Content-Type "text/html; charset=utf-8" "expr=%{CONTENT_TYPE} =~ m#^text/html$#"
</IfModule>header Content-Type "text/html; charset=utf-8"<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_typeapplies 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.
AddDefaultCharsetappends 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-8to 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.
Other http headers checks
- The caching instructions contradict each other
- The declared Content-Encoding may not match the body
- A compressible response was sent uncompressed
- The response has a body but no Content-Type
- The response sends obsolete headers, one of them harmful
- No Alt-Svc header advertises HTTP/3
- A response header leaks an internal address
- The response does not say how long it may be cached
- The response cannot be revalidated without downloading it again
- A compressed response does not vary on Accept-Encoding
- Vary: * makes the response uncacheable by any shared cache