HTTP заголовки ответа в PHP: как правильно настроить и избежать ошибок
Основной способ: функция header()
Как установить произвольный HTTP заголовок ответа?
Функция header() принимает строку с заголовком в формате "Имя: Значение". Вызов должен произойти до любого вывода данных (включая пробелы и HTML). Пример:
<?php
header('X-Custom-Header: MyValue');
?>
Php add header (добавление заголовка в php)
После этого заголовок будет передан браузеру. Если в скрипте уже был вывод (echo, print, пробелы перед <?php), возникнет ошибка Headers already sent.
Типичная проблема:
Ошибка "Cannot modify header information - headers already sent by (output started at ...)". Причина: вывод пробела, HTML или текста до вызова header(). Решение: проверить наличие пробелов перед <?php, перенести header() в самое начало скрипта, либо использовать буферизацию вывода с ob_start().
Альтернативные подходы и частные случаи
Как выполнить перенаправление (редирект)?
Для перенаправления используется заголовок Location. Третий параметр функции header() задаёт HTTP статус-код (по умолчанию 302). Пример:
<?php
header('Location: https://example.com/new-page', true, 301); // постоянный редирект
?>
Php http response header (установка http заголовков ответа в php)
После вызова header('Location:') следует завершить скрипт командой exit или die, чтобы предотвратить дальнейшее выполнение.
Ошибка: редирект не срабатывает, если перед header() был вывод. Решение: убедиться в отсутствии вывода, либо использовать ob_start().
Как установить Content-Type и кодировку?
Заголовок Content-Type управляет типом контента (HTML, JSON, XML и т.д.) и кодировкой символов:
<?php
header('Content-Type: text/html; charset=utf-8');
// или для JSON:
header('Content-Type: application/json; charset=utf-8');
?>
Если кодировка не указана, PHP по умолчанию отправляет ISO-8859-1, что может привести к некорректному отображению кириллицы. Рекомендуется всегда указывать charset=utf-8.
Как отправить несколько заголовков с одним именем?
По умолчанию header() заменяет предыдущий заголовок с тем же именем. Чтобы добавить ещё один (например, несколько Set-Cookie), нужно передать второй параметр false:
<?php
header('Set-Cookie: session=abc123', false);
header('Set-Cookie: user=john', false);
?>
При использовании false заголовки не перезаписываются, а добавляются.
Как установить статус-код ответа?
Для установки HTTP статус-кода используется функция http_response_code() (начиная с PHP 5.4). Пример:
<?php
http_response_code(404);
header('Content-Type: text/plain');
echo 'Страница не найдена';
?>
Также можно комбинировать с header('HTTP/1.1 404 Not Found'), но http_response_code() проще.
Как удалить ранее установленный заголовок?
Функция header_remove() удаляет заголовок по имени или все заголовки, если вызвана без аргументов:
<?php
header('X-Debug: 1');
// ... позже
header_remove('X-Debug'); // удалит конкретный заголовок
// header_remove(); удалит все заголовки
?>
Полезна при динамическом управлении заголовками в сложных приложениях.
Как установить cookie через заголовки?
Помимо функции setcookie(), можно вручную установить заголовок Set-Cookie. Это даёт полный контроль над параметрами.
<?php
$value = 'test';
$expires = time() + 3600;
header('Set-Cookie: mycookie=' . urlencode($value) . '; expires=' . gmdate('D, d M Y H:i:s T', $expires) . '; path=/; secure; httponly', false);
?>
При использовании setcookie() PHP сам формирует заголовок, но прямой вызов header() даёт гибкость (например, несколько значений в одной cookie).
Как избежать ошибки при уже начатом выводе?
Если вывод уже начат, но нужно изменить заголовки, применяется буферизация вывода. Функция ob_start() перехватывает весь вывод, и заголовки можно отправлять до момента сброса буфера.
<?php
ob_start(); // включить буферизацию
echo 'Некий контент';
header('Content-Type: text/html; charset=utf-8'); // теперь это возможно
ob_end_flush(); // отправить буфер
?>
Буферизация полезна при работе с шаблонизаторами, когда код заголовков расположен не в начале скрипта.
Как проверить, какие заголовки будут отправлены?
Функция headers_list() возвращает массив всех установленных заголовков (до их отправки). Это помогает отладке.
<?php
header('X-Test: 123');
$headers = headers_list();
print_r($headers);
?>
Результат можно посмотреть до вывода тела страницы.
Почему заголовок не применяется? (общие ошибки)
- Заголовок вызван после вывода - решение: переместить вызов или использовать ob_start.
- Некорректный синтаксис - заголовок должен содержать двоеточие после имени.
- Конфликт с настройками PHP (output_buffering) - проверьте php.ini.
- Заголовок перенаправления Location без exit - дальнейший код может выполняться.
Расширенные примеры с пояснениями
Пример 1: Настройка CORS заголовков
CORS (Cross-Origin Resource Sharing) позволяет разрешить запросы с других доменов. Типичная конфигурация для API:
<?php
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE');
header('Access-Control-Allow-Headers: Content-Type, Authorization');
header('Access-Control-Max-Age: 86400');
?>
Здесь разрешены все источники (*) и основные методы. Для продакшена следует указывать конкретный домен вместо звёздочки.
Пример 2: Отключение кэширования страницы
Чтобы браузер не кэшировал страницу, используются заголовки Cache-Control, Pragma и Expires:
<?php
header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
header('Pragma: no-cache');
header('Expires: Thu, 01 Jan 1970 00:00:00 GMT');
?>
Первый заголовок указывает, что данные не должны сохраняться в кэше (no-store), а второй - для совместимости с HTTP/1.0. Третий устанавливает дату в прошлом.
Пример 3: Защита от Clickjacking (X-Frame-Options)
Заголовок X-Frame-Options запрещает отображение страницы во фрейме:
<?php
header('X-Frame-Options: DENY');
// или SAMEORIGIN для разрешения фреймов того же домена
?>
Это стандартная практика безопасности.
Пример 4: Content-Security-Policy (CSP)
CSP позволяет ограничить источники контента (скриптов, стилей и т.д.) для защиты от XSS-атак:
<?php
header("Content-Security-Policy: default-src 'self'; script-src 'self' https://trusted.cdn.com; style-src 'self' 'unsafe-inline'");
?>
Приведённая политика разрешает скрипты только с собственного домена и указанного CDN, а стили - с self и inline.
Пример 5: Передача заголовков с помощью header_register_callback
Функция header_register_callback() позволяет зарегистрировать обработчик, который вызывается перед отправкой каждого заголовка. Это может быть полезным для логирования или модификации:
<?php
header_register_callback(function() {
error_log('Заголовок отправлен: ' . implode(', ', headers_list()));
});
header('X-Debug: 1');
?>
Коллбэк сработает при каждом вызове header(), даже если он не приводит к добавлению (например, при перезаписи).
Пример 6: Использование http_response_code() вместе с header()
Комбинированный пример для возврата JSON с кодом 422 (Unprocessable Entity):
<?php
http_response_code(422);
header('Content-Type: application/json; charset=utf-8');
$data = ['error' => 'Validation failed', 'details' => ['field' => 'email', 'message' => 'Invalid format']];
echo json_encode($data);
?>
Результат: заголовок Content-Type + JSON-тело + статус 422.
Пример 7: Установка заголовков с условной логикой (A/B тестирование)
В зависимости от параметра можно отправлять разные заголовки:
<?php
$variant = $_GET['variant'] ?? 'A';
if ($variant === 'B') {
header('X-Experience: variant_B');
} else {
header('X-Experience: variant_A');
}
?>
Результат: если в URL передать ?variant=B, заголовок будет соответствовать.
Пример 8: Передача файла через заголовки (Content-Disposition)
Для принудительной загрузки файла используется заголовок Content-Disposition:
<?php
$file = 'document.pdf';
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="' . basename($file) . '"');
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header('Content-Length: ' . filesize($file));
readfile($file);
exit;
?>
Результат: браузер предложит скачать файл document.pdf.