Question
I am setting up a new Linux server and want my web application to support UTF-8 correctly from end to end.
In the past, when configuring existing servers, I often ran into character encoding problems and eventually had to fall back to ISO-8859-1.
I want to understand exactly where encoding and character sets need to be configured. I know Apache, MySQL, and PHP are all involved, but I am looking for a clear checklist I can follow to avoid mismatches and to troubleshoot problems when they occur.
The environment is:
- Linux
- Apache 2
- PHP 5
- MySQL 5
Short Answer
By the end of this page, you will understand how UTF-8 must be configured consistently across the full request and response pipeline: browser, HTML, HTTP headers, PHP, database connection, database schema, and stored data. You will also learn a practical checklist for setting up UTF-8 correctly and how to diagnose common encoding mismatches.
Concept
Character encoding defines how text characters are stored as bytes. UTF-8 is the most common encoding for modern web applications because it can represent ASCII characters and also support text from many languages, symbols, and emoji.
The important idea is that UTF-8 only works reliably when every layer agrees on how text should be interpreted.
In a typical PHP web application, text moves through several layers:
- A browser sends form data or URL parameters.
- Apache serves the response.
- PHP reads and outputs strings.
- MySQL receives and stores text.
- The browser renders the returned HTML.
If even one layer assumes the wrong encoding, text can become corrupted or displayed incorrectly. This is often called mojibake.
A full UTF-8 setup usually involves these areas:
- HTML documents must declare UTF-8.
- HTTP response headers should specify UTF-8.
- PHP source files should be saved as UTF-8, ideally without BOM.
- MySQL database, tables, and columns should use a UTF-8-compatible character set.
- The database connection must explicitly tell MySQL that the client is sending and receiving UTF-8.
Why this matters in real programming:
- User names may include accented letters or non-Latin scripts.
- Product catalogs may contain multilingual data.
- APIs may send JSON with Unicode text.
- Logs, emails, and exports can break if encodings are inconsistent.
One important historical note: in MySQL 5, utf8 does not mean full UTF-8 support. It supports only up to 3-byte characters. Full Unicode support, including many emoji and some rare characters, requires utf8mb4. If possible, prefer utf8mb4 even on older-style PHP/MySQL stacks where it is supported.
Mental Model
Think of text encoding like labeling boxes during shipping.
- The text itself is the item inside the box.
- The encoding is the label that says how to interpret the contents.
- Every system that handles the box must read the same label correctly.
If one step says, "This box contains UTF-8," but another step opens it as ISO-8859-1, the contents look wrong even though the bytes may not have changed.
So the rule is simple:
- Store text as UTF-8
- Transmit text as UTF-8
- Declare text as UTF-8
- Read text as UTF-8
UTF-8 problems usually happen not because UTF-8 is difficult, but because one part of the chain silently assumes a different encoding.
Syntax and Examples
Core places to configure UTF-8
1. HTML output
Your HTML page should declare UTF-8:
<meta charset="UTF-8">
Place this near the top of the <head> section.
2. HTTP response header in PHP
Send the correct content type and charset:
<?php
header('Content-Type: text/html; charset=UTF-8');
This tells the browser how to interpret the response body.
3. MySQL connection charset
When connecting to MySQL, explicitly set the connection charset.
Using mysqli:
<?php
$mysqli = new mysqli('localhost', 'user', 'pass', 'app_db');
$mysqli->set_charset('utf8mb4');
Using PDO:
<?php
$pdo = new PDO(
'mysql:host=localhost;dbname=app_db;charset=utf8mb4',
'user',
'pass'
);
4. Database and table character set
Example SQL:
CREATE DATABASE app_db
CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
bio TEXT
) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
5. Apache default charset
Apache can send a default charset header, though many applications prefer to set this in PHP or the app itself.
Example Apache setting:
AddDefaultCharset UTF-8
Be careful: if your application already sends a charset header, you want both layers to agree.
Beginner-friendly example
<?php
header('Content-Type: text/html; charset=UTF-8');
$mysqli = new mysqli('localhost', 'user', 'pass', 'app_db');
$mysqli->set_charset('utf8mb4');
$name = 'Jürgen — こんにちは — مرحبا';
$stmt = $mysqli->prepare('INSERT INTO users (name) VALUES (?)');
$stmt->bind_param('s', $name);
$stmt->execute();
$result = $mysqli->query('SELECT name FROM users ORDER BY id DESC LIMIT 1');
$row = $result->fetch_assoc();
echo '<!DOCTYPE html>';
echo '<html><head><meta charset="UTF-8"><title>UTF-8 Test</title></head><body>';
echo '<p>' . htmlspecialchars($row['name'], ENT_QUOTES, 'UTF-8') . '</p>';
echo '</body></html>';
This example works correctly when:
- the PHP file is saved as UTF-8
- the database/table/column use
utf8mb4 - the MySQL connection is set to
utf8mb4 - the browser is told the page is UTF-8
Step by Step Execution
Consider this PHP code:
<?php
header('Content-Type: text/html; charset=UTF-8');
$mysqli = new mysqli('localhost', 'user', 'pass', 'app_db');
$mysqli->set_charset('utf8mb4');
$text = 'café';
$mysqli->query("INSERT INTO notes (content) VALUES ('" . $mysqli->real_escape_string($text) . "')");
$result = $mysqli->query('SELECT content FROM notes ORDER BY id DESC LIMIT 1');
$row = $result->fetch_assoc();
echo $row['content'];
Here is what happens step by step:
-
header('Content-Type: text/html; charset=UTF-8')- PHP sends an HTTP header telling the browser to interpret the response as UTF-8 HTML.
-
new mysqli(...)- PHP opens a connection to MySQL.
-
$mysqli->set_charset('utf8mb4')- PHP tells MySQL that text sent to and received from this connection uses UTF-8.
- This step is critical. Without it, the database connection may default to a different character set.
-
$text = 'café';- The PHP string contains a non-ASCII character:
é. - This is safe only if the source file is saved as UTF-8.
- The PHP string contains a non-ASCII character:
-
INSERT INTO notes ...- The text is sent from PHP to MySQL using the connection charset.
-
SELECT content ...- MySQL returns the bytes using the same connection charset.
-
echo $row['content'];- PHP outputs the text.
- Because the browser was told the page is UTF-8,
cafédisplays correctly.
If the page shows broken text such as café, that usually means the bytes were UTF-8 but some layer interpreted them as Latin-1.
Real World Use Cases
Where full UTF-8 support matters
User-generated content
Applications that accept names, messages, comments, or addresses must support many languages and accented characters.
Examples:
JoséFrançoisMiyazaki 宮崎Добрый день
E-commerce systems
Product names, customer details, and international shipping addresses often include characters outside ASCII.
APIs and JSON responses
Modern APIs frequently exchange Unicode text. If your backend stores or returns text in the wrong encoding, clients may receive corrupted data.
Content management systems
Blog posts, article titles, and editor content often include punctuation, smart quotes, symbols, and multilingual text.
Reporting and exports
CSV exports, emails, PDFs, and logs can fail or display incorrectly if the original data is not handled consistently as UTF-8.
Real Codebase Usage
In real projects, developers usually treat UTF-8 as a system-wide default, not as a one-off fix.
Common patterns
Set encoding at connection time
Applications usually set the DB charset immediately after connecting:
$mysqli->set_charset('utf8mb4');
This avoids relying on server defaults.
Centralize response headers
Frameworks or bootstrap files often set a default response charset in one place so every page is consistent.
header('Content-Type: text/html; charset=UTF-8');
Use utf8mb4 in schema definitions
Teams often define database defaults once and let tables inherit them.
ALTER DATABASE app_db CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci;
Validate imported data
When importing CSV files or reading external APIs, code often checks whether the source is really UTF-8 before storing it.
Escape output with encoding-aware functions
When outputting HTML, developers often use:
htmlspecialchars($value, ENT_QUOTES, 'UTF-8');
This handles both security and proper encoding awareness.
Troubleshooting pattern
When bugs appear, developers trace the text across stages:
- source file
- request input
- PHP string
- DB connection charset
- DB column charset
- output header
- browser rendering
This layer-by-layer approach is usually faster than guessing.
Common Mistakes
1. Setting the HTML meta tag but not the HTTP header
A <meta charset> tag helps, but it does not fix everything if the server sends a conflicting header.
Broken assumption:
<meta charset="UTF-8">
This alone is not enough if Apache or PHP sends charset=ISO-8859-1.
How to avoid it:
- Check the actual HTTP response headers.
- Make sure server and application agree.
2. Forgetting to set the MySQL connection charset
This is one of the most common causes of corrupted text.
Broken code:
<?php
$mysqli = new mysqli('localhost', 'user', 'pass', 'app_db');
// Missing set_charset()
Fix:
<?php
$mysqli = new mysqli('localhost', 'user', 'pass', 'app_db');
$mysqli->set_charset('utf8mb4');
3. Using MySQL utf8 and assuming it means full UTF-8
In MySQL 5, utf8 is limited and cannot store all Unicode characters.
How to avoid it:
- Prefer
utf8mb4if available.
4. Saving PHP files in the wrong encoding
If your source file itself is saved as ISO-8859-1 or another encoding, string literals may already be broken before PHP sends them anywhere.
How to avoid it:
- Save source files as UTF-8 without BOM.
- Configure your editor to use UTF-8 by default.
5. Converting already-correct UTF-8 again
Developers sometimes call conversion functions unnecessarily and damage the text.
Broken idea:
$text = utf8_encode($text);
If $text is already UTF-8, this can corrupt it.
6. Mixing encodings in imported data
A database can be configured for UTF-8 while imported CSV files are still in Windows-1252 or ISO-8859-1.
How to avoid it:
- Detect or normalize external input before storing it.
- Test imports with accented characters.
7. Not checking existing data
Changing table charset does not always fix previously corrupted rows.
Important point:
- Correct configuration prevents new corruption.
- Existing bad data may need cleanup or migration.
Comparisons
UTF-8-related settings compared
| Layer | What it controls | Example | Why it matters |
|---|---|---|---|
| HTML meta tag | How the browser should interpret the page | <meta charset="UTF-8"> | Helps browser rendering |
| HTTP header | Response charset declared by server/app | Content-Type: text/html; charset=UTF-8 | Often takes priority over HTML |
| PHP source file encoding | How string literals are stored in code | Save file as UTF-8 | Prevents broken literals |
| MySQL connection charset | How client and DB exchange text | $mysqli->set_charset('utf8mb4') | Prevents corruption during queries |
| Database/table/column charset | How text is stored in MySQL | CHARACTER SET utf8mb4 | Ensures data can be stored correctly |
MySQL utf8 vs utf8mb4
| Option | Meaning in MySQL 5 | Recommended? |
|---|---|---|
utf8 | 3-byte subset of UTF-8 | Usually no |
utf8mb4 | Full UTF-8 support | Yes |
Apache default charset vs PHP header
| Option | Best use | Notes |
|---|---|---|
Apache AddDefaultCharset UTF-8 | Site-wide default | Good if app does not override headers |
PHP header() | Per-response control | More explicit in app code |
In many applications, setting headers in PHP gives more precise control, but either way the main goal is consistency.
Cheat Sheet
UTF-8 setup checklist
- Save PHP, HTML, CSS, and JS files as UTF-8
- Prefer UTF-8 without BOM for source files
- Send HTTP headers with
charset=UTF-8 - Add
<meta charset="UTF-8">in HTML - Set MySQL connection charset to
utf8mb4 - Create databases and tables with
utf8mb4 - Use
htmlspecialchars(..., ENT_QUOTES, 'UTF-8')for HTML output - Verify imported files are really UTF-8
PHP
header('Content-Type: text/html; charset=UTF-8');
$mysqli->set_charset('utf8mb4');
PDO
$pdo = new PDO(
'mysql:host=localhost;dbname=app_db;charset=utf8mb4',
'user',
'pass'
);
MySQL schema
CREATE DATABASE app_db
CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;
Apache
AddDefaultCharset UTF-8
Common symptom
caféusually means UTF-8 bytes were read as Latin-1.
Key rule
If text is broken, check all layers:
- Source file
- Input encoding
- PHP output header
- MySQL connection charset
- DB/table/column charset
- Browser rendering
FAQ
Should I configure UTF-8 in both HTML and HTTP headers?
Yes. The HTTP header is very important, and the HTML meta tag is a useful backup inside the document.
Is MySQL utf8 the same as full UTF-8?
No. In MySQL 5, utf8 is only a 3-byte subset. Use utf8mb4 for full Unicode support.
Why do I see text like é instead of é?
That usually means UTF-8 data was decoded using ISO-8859-1 or a similar encoding.
Do I need to change Apache if PHP already sends UTF-8 headers?
Not always. If PHP sends the correct header consistently, Apache does not need to override it. The important part is that they do not conflict.
Does changing the database charset fix old broken data?
Usually not. It helps prevent future problems, but already corrupted rows may need repair or re-import.
Should PHP files be saved as UTF-8?
Yes. If your source files contain non-ASCII text, they should be saved as UTF-8, ideally without BOM.
How can I test whether my setup is correct?
Insert and retrieve sample text containing accents and non-Latin characters, such as Jürgen, café, こんにちは, and مرحبا.
Is UTF-8 enough for JSON APIs too?
Yes. JSON is commonly exchanged as UTF-8, so keeping your app internally consistent with UTF-8 is the safest approach.
Mini Project
Description
Build a small PHP page that stores and displays multilingual text from a MySQL database using UTF-8 correctly. This project demonstrates the complete path: HTML output, PHP headers, database connection charset, and database schema configuration.
Goal
Create a working PHP page that inserts and reads multilingual text without corruption.
Requirements
- Create a MySQL database and table that use
utf8mb4 - Connect to MySQL from PHP and set the connection charset to
utf8mb4 - Output an HTML page with UTF-8 headers and a UTF-8 meta tag
- Insert at least one string containing accented and non-Latin characters
- Read the stored value back and display it safely in HTML
Keep learning
Related questions
Are PDO Prepared Statements Enough to Prevent SQL Injection in PHP?
Learn how PDO prepared statements prevent SQL injection in PHP, what they protect, and the mistakes that still leave MySQL apps vulnerable.
Can You Bind an Array to an IN Clause in PHP PDO?
Learn how PDO handles placeholders in IN() clauses, why arrays cannot be bound directly, and the safe PHP pattern to build dynamic queries.
Choosing the Right MySQL Collation for PHP and UTF-8
Learn how MySQL character sets and collations work with PHP, and how to choose a practical UTF-8 setup for web applications.