Zeichenkodierung im Web: UTF-8, Umlaute und Mojibake reparieren

Zeichenkodierung im Web: UTF-8, Umlaute und Mojibake reparieren

Aus „Größe“ wird „Größe“, aus dem Euro „€“, und im Kontaktformular landen Rauten mit Fragezeichen: Kaputte Umlaute sind fast nie ein Schriftproblem, sondern ein Widerspruch zwischen der Kodierung, in der Text gespeichert wurde, und der, mit der er gelesen wird. Wer versteht, an welchen vier Stellen die Kodierung festgelegt wird, findet den Fehler in Minuten.

Von ASCII zu Unicode in drei Schritten

ASCII kennt 128 Zeichen: lateinische Buchstaben ohne Akzente, Ziffern, Satzzeichen, Steuerzeichen. Umlaute fehlen. Die 8-Bit-Zeichensätze der Normreihe ISO 8859 nutzten deshalb die oberen 128 Plätze für Sonderzeichen, ISO-8859-1 (Latin-1) für westeuropäische Sprachen. ISO-8859-15 ersetzt einige selten genutzte Zeichen, unter anderem durch das Euro-Zeichen. Microsofts Windows-1252 legt zusätzliche Zeichen wie €, typografische Anführungszeichen und Gedankenstriche in einen Bereich, den ISO-8859-1 für Steuerzeichen reserviert.

Alle diese Zeichensätze haben dasselbe Problem: Ein Byte kann nur 256 Werte annehmen, und derselbe Wert bedeutet je nach Tabelle etwas anderes. Unicode löst das, indem es jedem Zeichen aller Schriften eine eigene Nummer gibt. Version 18.0 vom September 2026 umfasst laut Unicode-Konsortium 172.808 Zeichen. UTF-8 ist die Art, diese Nummern als Bytes zu speichern: ASCII-Zeichen bleiben ein Byte und damit unverändert, Umlaute brauchen zwei, das Euro-Zeichen drei, Emoji vier.

Schon HTML 4.01 legte 1999 fest, dass der Zeichenvorrat eines HTML-Dokuments ISO 10646 ist, also Zeichen für Zeichen Unicode. Welche Byte-Kodierung eine Datei nutzt, musste aber separat über HTTP oder ein META-Element angegeben werden. Heute ist die Frage entschieden: Der WHATWG Encoding Standard schreibt vor, dass neue Formate ausschließlich UTF-8 verwenden, und laut W3Techs nutzen im Oktober 2026 99,1 Prozent der Websites mit erkennbarer Kodierung UTF-8.

Eine Besonderheit, die Fehler verschleiert

Browser behandeln die Angaben iso-8859-1, latin1 und sogar us-ascii laut Encoding Standard als Windows-1252. Eine als ISO-8859-1 deklarierte Seite zeigt also auch Zeichen wie typografische Anführungszeichen korrekt an, die streng genommen gar nicht in Latin-1 enthalten sind. Ein PHP-Skript oder eine Datenbank, die Latin-1 wörtlich nimmt, macht daraus dagegen Unsinn. ISO-8859-15 ist eine eigene Kodierung und kein Alias.

Wo die Kodierung festgelegt wird

Eine Seite durchläuft vier Stationen, und an jeder muss UTF-8 stehen:

  1. Die Datei selbst: Der Editor muss als UTF-8 speichern. Die Angabe im Quelltext ändert die Bytes nicht.
  2. Die Deklaration im HTML: <meta charset='utf-8'> direkt nach <head>, laut W3C vollständig in den ersten 1024 Bytes.
  3. Der HTTP-Header: Content-Type: text/html; charset=utf-8. Er hat Vorrang vor dem meta-Element.
  4. Datenbank und Skriptsprache: Tabellen, Spalten und die Verbindung zwischen Anwendung und Datenbank.

Die Rangfolge im Browser ist laut W3C: Byte Order Mark vor HTTP-Header vor meta-Element. Sendet der Server charset=ISO-8859-1, hilft das korrekte meta-Tag also nicht. Das ist die häufigste Ursache, wenn eine Seite lokal stimmt und auf dem Server nicht.

<!DOCTYPE html>
<html lang='de'>
<head>
  <meta charset='utf-8'>
  <title>Größentabelle</title>
</head>

Auf Apache-Servern setzt eine Zeile in der Konfiguration oder in der .htaccess den Header für text/html und text/plain, sofern noch keiner angegeben ist:

AddDefaultCharset UTF-8

Den tatsächlich gesendeten Header zeigen die Entwicklertools des Browsers im Netzwerk-Tab oder curl -I https://example.com/ auf der Kommandozeile.

BOM: drei unsichtbare Bytes

Die Byte Order Mark ist bei UTF-8 die Bytefolge EF BB BF am Dateianfang. Für Browser ist sie ein eindeutiges Signal und schlägt sogar den HTTP-Header. Das W3C empfiehlt, das meta-Element trotzdem zu behalten. Ärger macht die BOM in PHP-Dateien, wo sie vor dem öffnenden <?php als Ausgabe gilt und spätere Header-Aufrufe scheitern lässt, sowie in CSV-Dateien und Shell-Skripten. Die pragmatische Regel: HTML darf eine BOM haben, Skripte und Konfigurationsdateien sollten ohne gespeichert werden. Gute Editoren bieten beide Varianten unter „Kodierung“ an.

Typische Fehlerbilder und ihre Ursache

Das Muster der kaputten Zeichen verrät, in welche Richtung der Fehler läuft. Die Byte-Folgen in der Tabelle lassen sich mit jeder Programmiersprache nachrechnen.

AnzeigeGemeintUrsache
ä ö ü ßä ö ü ßUTF-8-Bytes werden als Windows-1252 oder Latin-1 gelesen, meist falscher HTTP-Header oder falsche Datenbankverbindung
€€dasselbe, das Euro-Zeichen hat in UTF-8 drei Bytes
äädoppelt kodiert: bereits kaputter Text wurde nochmals nach UTF-8 umgewandelt
� (Raute mit Fragezeichen)äLatin-1-Bytes werden als UTF-8 gelesen, etwa eine alte Datei in neuer Umgebung
Fehler „Incorrect string value“ oder abgeschnittener TextEmoji, etwa U+1F600MySQL-Spalte mit utf8 (utf8mb3) statt utf8mb4
&auml; sichtbar im TextäHTML-Entity wurde doppelt maskiert, etwa durch zweifaches htmlspecialchars()

Reparieren: Editor, Datenbank, PHP

Datenbank auf utf8mb4 umstellen

MySQLs Zeichensatz utf8 ist ein Alias für utf8mb3: höchstens drei Bytes pro Zeichen, keine Emoji und keine Zeichen außerhalb der Basisebene. Das MySQL-8.4-Handbuch führt utf8mb3 als veraltet und empfiehlt für alle neuen Anwendungen utf8mb4. Die Umstellung einer Tabelle:

-- vorher Sicherung anlegen
ALTER TABLE artikel CONVERT TO CHARACTER SET utf8mb4;

Zwei Fallen: Indexlängen sind in Bytes begrenzt, bei älteren InnoDB-Zeilenformaten passen laut Handbuch nur noch 191 statt 255 Zeichen in einen Index. Und die Verbindung muss ebenfalls utf8mb4 sprechen, in PHP etwa über den DSN mysql:host=localhost;dbname=shop;charset=utf8mb4. Steht dort nichts, kann die Verbindung mit einem anderen Zeichensatz laufen, und genau so entsteht „ä“ in Datensätzen, die in der Datenbank korrekt aussehen. Doppelt kodierte Inhalte repariert CONVERT TO übrigens nicht, die müssen gezielt zurückkonvertiert werden. Vorher an einer Kopie testen.

PHP und Editor

Seit PHP 5.6 ist die Einstellung default_charset standardmäßig UTF-8. Probleme entstehen meist durch alte Funktionen wie utf8_decode() oder durch Byte-Funktionen wie substr(), die einen Umlaut mitten durchschneiden. Für Text gehören die mb_-Varianten wie mb_substr() verwendet. Im Editor lohnt ein Blick auf die angezeigte Kodierung: VS Code zeigt sie in der Statusleiste, Notepad++ im Menü „Kodierung“, und beide können eine Datei nach UTF-8 umwandeln. Nur das Umwandeln ändert die Bytes, das bloße Umstellen der Anzeige nicht.

HTML-Entities: wann sie noch nötig sind

Mit UTF-8 braucht niemand mehr &auml; oder &szlig;. Das W3C rät, Zeichen in ihrer normalen Form zu schreiben und Maskierungen nur für drei Fälle zu nutzen:

  • &lt;, &gt; und &amp; immer, wenn die Zeichen als Text erscheinen sollen. Das gilt auch für das & in URL-Parametern im href.
  • &quot; in Attributwerten, die selbst in doppelten Anführungszeichen stehen.
  • Unsichtbare oder leicht verwechselbare Zeichen wie das geschützte Leerzeichen &nbsp; oder Richtungsmarken in arabischem oder hebräischem Text.

Bei HTML-Mails ist die Lage ähnlich, dort entscheidet zusätzlich der MIME-Header über die Kodierung, mehr dazu im Beitrag HTML-E-Mails oder nur Text. Auch XML setzt standardmäßig auf UTF-8, wie der Beitrag zu den XML-Grundlagen zeigt.

Quellen