Why your Cyrillic turns into "Привет" and how to fix it once and for all
\n\nHave you ever opened your PHP website and instead of the familiar "Hello, world!" seen something like Привет, РјРёСЂ!? If so — congratulations, you've encountered the classic UTF-8 all the way through problem. This is that situation where the encoding breaks somewhere along the path from the database to the browser, and everyone is to blame, but no one wants to take responsibility.
On Stack Overflow, this question has gathered thousands of views and hundreds of answers. And for good reason. Even experienced developers sometimes get confused with encoding settings. In this tutorial, we'll break down a step-by-step recipe for making PHP, MySQL, and the browser work together so that UTF-8 works everywhere and always. No magic — just the right settings.
\n\nStep 1. Database: the foundation of your encoding
\n\nIt all starts with the database. If your MySQL stores data in latin1 or cp1251, and you try to output it in UTF-8 — you'll get mojibake. But even if the database is in UTF-8, there's a nuance: the old utf8 encoding in MySQL does not support emoji characters and some rare hieroglyphs. The solution is utf8mb4.
1.1. Creating tables with the correct encoding
\n\nWhen creating a new table, always specify:
\n\nCREATE TABLE articles (\n id INT AUTO_INCREMENT PRIMARY KEY,\n title VARCHAR(255) NOT NULL,\n content TEXT NOT NULL\n) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;\n\nAn alternative is to set the default encoding for the entire database:
\n\nALTER DATABASE your_database_name CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;\n\nImportant: utf8mb4_unicode_ci is a collation (comparison rule). It correctly handles all Unicode characters. If you don't need sorting by different languages, utf8mb4_general_ci will also work.
1.2. Converting existing tables
\n\nIf you already have data in a different encoding, first convert it:
\n\nALTER TABLE articles CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;\n\nBut be careful: if the table already had data in latin1, it may turn into double encoding (e.g., Привет). In that case, you first need to convert the columns to binary, and then to utf8mb4. That's a separate story, but for simplicity, let's assume you're starting from scratch.
Step 2. PHP database connection: configuring PDO
\n\nIn modern PHP, you use PDO (or mysqli). And here's a critical point: after connecting to the database, you need to explicitly specify the encoding. Otherwise, PHP will communicate with MySQL in the default encoding (often latin1).
\n\n2.1. Correct PDO connection string
\n\n$host = 'localhost';\n$dbname = 'your_database';\n$username = 'root';\n$password = '';\n\n$dsn = "mysql:host=$host;dbname=$dbname;charset=utf8mb4";\n\n$options = [\n PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,\n PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,\n PDO::ATTR_EMULATE_PREPARES => false,\n];\n\ntry {\n $pdo = new PDO($dsn, $username, $password, $options);\n} catch (PDOException $e) {\n echo "Connection failed: " . $e->getMessage();\n}\n\nNotice the charset=utf8mb4 in the DSN. This forces PHP and MySQL to agree on the encoding at the connection level.
2.2. Alternative method (if DSN doesn't work)
\n\nSometimes older versions of PHP or servers don't read the charset from the DSN. In that case, add this after connecting:
\n\n$pdo->exec("SET NAMES 'utf8mb4'");\n\nBut the first method is preferable — it's cleaner and more modern.
\n\nStep 3. HTTP headers and HTML: telling the browser the truth
\n\nEven if the database and PHP work in UTF-8, the browser might decide that