/ ← HTML Checker Fixes / Encoding Errors
In this guide
  1. Reading the symptoms
  2. How a browser decides the encoding
  3. The checker messages and what to do about each
  4. The fix, step by step
  5. Server and application settings
  6. The byte order mark: allowed, but watch PHP
  7. Edge cases worth knowing
  8. Preventing it
  9. Questions and answers

Reading the symptoms

The garbled characters are not random. Each pattern points to a particular mismatch, so it is worth looking closely before changing anything.

What visitors seeWhat it should sayWhat happened
£, ©, a stray  before spaces£, ©, a non-breaking spaceUTF-8 text read as windows-1252 or ISO-8859-1. Two-byte characters show as two symbols.
’, “, —’, “, —The same mismatch for three-byte characters such as curly quotes and dashes.
é, è, üé, è, üAccented letters in UTF-8 read as a single-byte encoding.
 and longer chainsOne characterDouble encoding: text was misread once, saved, then converted to UTF-8 again.
� (a diamond with a question mark)Any accented or special characterThe reverse: windows-1252 text read as UTF-8. The byte is not valid UTF-8, so the browser shows the replacement character.
? in place of letters or emojiThe original characterThe character was lost when it was saved, often by a database or export using an encoding that cannot hold it.

The first five are display problems: the right bytes exist somewhere and are being misread, so fixing the declaration may be enough. The last one is data loss. Question marks saved into a database cannot be turned back into the original letters; you need a backup or the original source.

💡 If only some text is broken, note which parts. Template text fine but product descriptions broken points at the database. Everything broken points at the header or the meta tag. One old page broken points at that file.

How a browser decides the encoding

A browser has to choose an encoding before it can read the page, so it checks several places in a fixed order and uses the first one it finds:

  1. A byte order mark. If the file starts with the UTF-8 BOM, the browser uses UTF-8 and ignores everything else. A BOM is allowed in HTML, including before the doctype.
  2. The HTTP header. A charset parameter on the Content-Type response header, for example text/html; charset=utf-8.
  3. The meta tag. <meta charset="utf-8">, which the browser looks for by pre-scanning the start of the document.
  4. A guess. If none of these exist, the browser guesses from the content, the user's locale and other signals. In the UK that guess is often windows-1252.

This order explains the most confusing situations. A page can have a perfect meta tag and still display mojibake, because the server's header says ISO-8859-1 and the header wins. A file can declare windows-1252 in its meta tag and display correctly as UTF-8, because an editor added a BOM. The fix is to make all three agree, not to adjust whichever one you happen to find first.

The 1024-byte rule

The browser's pre-scan for the meta tag only reads the first 1024 bytes of the document. If the charset declaration comes after a long block of inline CSS, a large comment, a set of preload links or a tracking snippet, the browser may never see it and falls back to guessing. The HTML specification requires the declaration to sit entirely within those first 1024 bytes, which is why the HTML Checker flags a late declaration even when the value is right.

The checker messages and what to do about each

MessageMeaningFix
The character encoding was not declaredNo BOM, no charset in the header and no meta tag. The checker, like a browser, has to guess.Add <meta charset="utf-8"> as the first element in the head and set the header.
A charset attribute on a meta element found after the first 1024 bytesThe declaration exists but is too far down the page.Move it directly after the opening <head> tag.
Legacy encoding windows-1252 used. Documents must use UTF-8The page is valid in its own encoding, but HTML now requires UTF-8.Convert the files and data to UTF-8, then change the declarations.
Internal encoding declaration disagrees with the actual encoding of the documentThe meta tag names one encoding, but the header or BOM made the page use another.Decide on UTF-8 and correct whichever layer is wrong.
Malformed byte sequenceThe page is declared UTF-8 but contains bytes that are not valid UTF-8.Find the file, include or database field holding legacy-encoded text and convert it.
A meta element with http-equiv content-type and a charset attribute both presentTwo declarations in the head.Keep only the short meta charset form.

The long form <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> is still valid, but it does exactly the same job as the short form. Old themes often carry both, sometimes with different values. Delete the long one.

The fix, step by step

Step 1

Identify the pattern of broken characters

Before you change anything, collect examples of the broken text and where they appear. Compare them with the symptoms table: sequences starting with  or à mean UTF-8 is being read as a legacy encoding, the diamond replacement character means the opposite, and plain question marks mean the characters were lost when saved. Note whether the problem affects the whole site, one template, database content only or a single old page.

Step 2

Check the header, the meta tag and the BOM

Run curl -sI against the page and read the Content-Type line, then view the source and find the meta charset tag. Finally check the first bytes of the response for a byte order mark. Write down all three values. Any disagreement between them, or a missing value, is a cause; remember that a BOM overrides the header, and the header overrides the meta tag.

Step 3

Declare UTF-8 in the first 1024 bytes

In the base layout or header template, make <meta charset="utf-8"> the very first element inside <head>, before the title, inline styles, comments, preload hints and analytics code. Remove any older http-equiv content type tag and any second charset tag added by a plugin. Placing it first guarantees the browser finds it during its pre-scan, whatever else is later added to the head.

Step 4

Make the server send charset=utf-8

Configure the web server or application so HTML responses carry Content-Type: text/html; charset=utf-8. On Apache that is AddDefaultCharset UTF-8; on nginx it is charset utf-8; in the server block; in PHP set default_charset to UTF-8. Also check CDN or proxy rules that rewrite headers. Test again with curl to confirm the header changed on the live address, not just locally.

Step 5

Convert template and content files to UTF-8

Find files saved in a legacy encoding with file -i on the server, or by opening them in an editor that shows the encoding in its status bar. Convert them with the editor's save with encoding option or with iconv -f WINDOWS-1252 -t UTF-8, writing to a new file and checking it before replacing the original. Include CSV imports, translation files and old static pages, which are often overlooked.

Step 6

Align the database connection and tables

In MySQL or MariaDB, check the table and column character sets and the connection character set. Use utf8mb4 rather than the older utf8, which cannot store four-byte characters such as emoji. Set the connection to utf8mb4 in your application, for example the DB_CHARSET constant in WordPress or the charset in a PDO connection string, because a mismatched connection garbles text on the way in and out.

Step 7

Repair text that was stored damaged

Once new text is saved correctly, fix old records that already contain mojibake. For a small number, edit them by hand. For many, run a search and replace on a copy of the database, mapping sequences such as ’ back to the right character, or convert double-encoded columns in a controlled way. Always take a backup first, test on staging, and rerun the HTML Checker on affected pages.

Server and application settings

The header is the layer most often forgotten, because it is invisible in the page source. These are the usual places to set it.

# Apache (.htaccess or virtual host)
AddDefaultCharset UTF-8
AddCharset UTF-8 .html .css .js

# nginx (server block)
charset utf-8;
charset_types text/html text/css application/javascript;

# PHP (php.ini)
default_charset = "UTF-8"

# Check the result
curl -sI https://www.example.co.uk/ | grep -i content-type
content-type: text/html; charset=utf-8

Stylesheets and scripts can have encoding problems too. A CSS file containing a pound sign in a content property, or a script containing an accented string, needs to be served as UTF-8 or begin with @charset "UTF-8"; in the case of CSS. JSON responses are UTF-8 by definition.

Frameworks and platforms

The byte order mark: allowed, but watch PHP

A UTF-8 byte order mark is three bytes at the start of a file that announce it as UTF-8. HTML allows it, and browsers treat it as the strongest signal of all. For a static HTML file it is harmless and can even help.

The problem is with server-side includes. PHP sends everything outside its tags as output, and a BOM counts. If functions.php, wp-config.php or a language file starts with a BOM, those three bytes are sent before the page, which leads to the familiar "headers already sent" warning, broken redirects and cookies that fail to set. Because the bytes are invisible, the page source just looks as if something strange is going on at the top.

# List PHP files in a theme that start with a UTF-8 BOM
grep -rlI $'^\xEF\xBB\xBF' --include=*.php wp-content/themes/larchfield-child/

Most code editors have an option to save as "UTF-8" rather than "UTF-8 with BOM". Use that for code files, and re-save any file the command above finds.

Edge cases worth knowing

⚠️ Take a full backup before converting database tables or running search and replace on content. Encoding repairs that go wrong can turn recoverable mojibake into permanent question marks.

Preventing it

Questions and answers

1. Why does my pound sign show as £?

Your text is stored as UTF-8, where the pound sign takes two bytes, but the browser is reading it with a single-byte encoding such as windows-1252, so each byte becomes its own character. The first one displays as Â. Check the Content-Type header your server sends and the meta charset tag in your head. Usually one of them is missing or names ISO-8859-1. Make both say UTF-8 and the pound sign will display correctly without changing the text.

2. What causes the black diamond question mark symbol?

The diamond with a question mark is the Unicode replacement character. Browsers show it when a page is declared as UTF-8 but contains bytes that are not valid UTF-8, typically text saved in windows-1252 by an old editor, a legacy include file or content imported from a spreadsheet. The fix is to convert that file or data to UTF-8 rather than to change the page declaration, because the rest of the site is correctly UTF-8 and would break if you switched.

3. Does the meta charset have to be the first thing in the head?

Strictly, the specification requires it to fall within the first 1024 bytes of the document, and it must come before any text that uses special characters, such as the title. Putting it first inside the head is the simplest way to guarantee both, whatever scripts, styles and comments get added later. If a plugin or tag manager injects large blocks above it, the browser may miss it during its pre-scan and guess the encoding instead.

4. Is a byte order mark before the doctype an error?

No. HTML permits a UTF-8 byte order mark at the very start of the document, before the doctype, and browsers use it to identify the encoding with the highest priority. It is not needed if your server sends the correct header and your meta tag is in place. The only real problem is in PHP files and other server-side includes, where the invisible bytes are sent as output and can trigger headers already sent warnings.

5. Should I use utf8 or utf8mb4 in MySQL?

Use utf8mb4. In MySQL, the older utf8 character set only stores characters up to three bytes long, so emoji and some rarer characters cannot be saved and are lost or rejected. utf8mb4 is full UTF-8. Convert both the tables and columns and make sure the application connects using utf8mb4, because a correct table with a mismatched connection still garbles text. Back up the database first and test the conversion on a staging copy.

6. Why is only some of my text garbled?

Because different parts of the page come from different places with different encodings. If template text is fine but product descriptions or blog posts are broken, the database or the connection to it is the likely cause. If one old page is broken, that file was probably saved in a legacy encoding. If a single widget is wrong, its content comes from an external source. Trace the broken text to where it is stored and fix that layer.

7. Will fixing the encoding fix text already saved as question marks?

No. When a character is replaced by a question mark during saving, the original information is gone, and no encoding change can bring it back. That is different from mojibake such as £ or ’, where the right bytes still exist and can be reinterpreted or mapped back. For question marks, restore the text from a backup, the original source document or the supplier file, after fixing the layer that lost it in the first place.

Check your page encoding

The HTML Checker validates your markup against W3C standards and reports errors and warnings covering the doctype, character encoding, tag nesting, ARIA, forms and the language attribute, so you can fix each template at its source.

Open HTML Checker Fixes →
💬 Got a problem?
Related Guides: All HTML Checker Fixes  ·  Fixing a Missing or Wrong Doctype  ·  Fixing the lang Attribute  ·  HTML Checker Guide  ·  Learning Hub

About aiwebpageseo

aiwebpageseo.com is a data-driven SEO and AEO (Answer Engine Optimisation) platform providing a free suite of technical SEO tools. Rather than relying on AI-theorised assumptions, the platform analyses live URL performance, delivering objective diagnostics, page speed metrics, CLS debugging, and site crawl data alongside actionable technical tutorials.