Знакомьтесь: "Headers already sent" — ваш новый враг (и друг)
Если вы хоть раз писали на PHP, вы наверняка встречались с этой ошибкой. Она появляется внезапно, часто в самый неподходящий момент — когда вы уже почти закончили скрипт. Сообщение пугающее, красное, и кажется, что весь код сломался. Но на самом деле это одна из самых полезных ошибок в PHP. Она учит вас правильно работать с HTTP-протоколом.
В этом туториале мы разберём, почему возникает ошибка Headers already sent, как её диагностировать и, главное, как исправить раз и навсегда. Мы не просто скопируем ответ со Stack Overflow — мы поймём суть. Поехали!
Что такое HTTP-заголовки и почему PHP на них ругается?
Прежде чем лечить, нужно понять анатомию. Когда PHP отправляет ответ браузеру, он делает это в два этапа:
- Сначала — заголовки (headers). Это служебная информация: тип контента (
Content-Type), кодировка, куки, статус ответа (200 OK, 404 Not Found). - Потом — тело ответа (body). Это сам HTML, текст, JSON — то, что видит пользователь.
Проблема в том, что PHP не может отправить заголовки после того, как начал отправлять тело. Если вы попытаетесь вызвать header() после того, как уже вывели хоть один символ (даже пробел), PHP выдаст ошибку:
Warning: Cannot modify header information - headers already sent by (output started at /path/to/file.php:12)
Вот и вся магия. Ошибка не фатальная — она просто предупреждает, что ваш header() не сработал. Но последствия могут быть серьёзными: редирект не выполнится, кука не установится, JSON-ответ сломается.
Топ-5 причин, почему это происходит
Давайте разберём самые частые сценарии. Узнаёте себя?
1. Пробел или пустая строка перед <?php
Это классика. Вы открываете файл, а перед открывающим тегом <?php стоит пробел, пустая строка или символ BOM (Byte Order Mark). PHP воспринимает этот символ как часть вывода — и понеслось.
// ❌ Ошибка: пробел перед <?php
<?php
header('Location: /new-page.php');
?>
// ✅ Правильно: никаких символов до <?php
<?php
header('Location: /new-page.php');
?>
2. Вывод через echo или print до header()
Логичная, но частая ошибка новичков: сначала что-то вывести, а потом попытаться перенаправить пользователя.
<?php
echo "Привет, мир!";
header('Location: /thanks.php'); // ❌ Ошибка: вывод уже был
?>
3. HTML-код до PHP-блока
В файле может быть смешанный код: сначала HTML-разметка, потом PHP-логика с header().
<!DOCTYPE html>
<html>
<head>
<title>Мой сайт</title>
</head>
<body>
<?php
header('Location: /login.php'); // ❌ Ошибка: HTML уже отправлен
?>
</body>
</html>
4. Ошибки PHP и функции вроде var_dump()
Скрипт может выводить предупреждения или отладочную информацию до вызова header(). Например, если у вас включён display_errors и PHP выводит warning.
<?php
var_dump($some_variable); // Вывод данных
header('Content-Type: application/json'); // ❌ Ошибка
?>
5. Невидимые символы в подключаемых файлах (include/require)
Вы подключаете файл с помощью include или require, а в конце этого файла есть лишняя пустая строка или пробел после закрывающего ?>.
// config.php
<?php
$db_host = 'localhost';
?>
// ← Вот этот пробел после ?> всё ломает!
Как найти источник проблемы: пошаговая диагностика
Самое главное — ошибка всегда указывает, где именно начался вывод. Читайте сообщение внимательно: output started at /path/to/file.php:12 — это строка 12 вашего файла. Откройте её и посмотрите, что там.
Шаг 1. Проверьте файл, указанный в ошибке
Откройте конкретный файл и строку. Скорее всего, вы увидите echo, пробел, HTML или пустую строку.
Шаг 2. Временно включите все ошибки
Добавьте в начало скрипта:
<?php
error_reporting(E_ALL);
ini_set('display_errors', 1);
?>
Это покажет все предупреждения, которые могут быть причиной неявного вывода.
Шаг 3. Используйте функцию headers_sent()
Перед вызовом header() проверьте, не были ли уже отправлены заголовки:
<?php
if (!headers_sent()) {
header('Location: /new-page.php');
exit;
} else {
echo "Заголовки уже отправлены. Редирект невозможен.";
}
?>
Три способа исправить ошибку (от простого к продвинутому)
Теперь переходим к самому интересному — как это починить. У вас есть три пути.
Способ 1. Перенесите header() до любого вывода (рекомендуемый)
Это правильное архитектурное решение. Функции, изменяющие заголовки, должны вызываться в самом начале скрипта, до любого HTML или текста.
<?php
// Всё, что меняет заголовки — в самом начале
header('Content-Type: text/html; charset=utf-8');
session_start();
// Потом — остальной код
echo "Добро пожаловать!";
?>
Если вам нужно сделать редирект после какой-то логики — вынесите её до вывода:
<?php
// Логика проверки
$user_logged_in = false;
if (!$user_logged_in) {
header('Location: /login.php');
exit; // Всегда вызывайте exit после header('Location:')
}
// HTML только после проверок
?>
<!DOCTYPE html>
<html>...</html>
Способ 2. Используйте буферизацию вывода (ob_start)
Если вы не можете контролировать весь код (например, работаете с легаси-проектом или чужой библиотекой), включите буферизацию. Весь вывод будет накапливаться в буфере и отправляться только после завершения скрипта.
<