skip to content

In PHP, what causes the "Cannot modify header information - headers already sent" warning, and how do you fix it?

level: juniorimportance: must knowfreq 72%

answer

  1. headers leave with the first body byte
  2. whitespace before <?php, or after ?>
  3. a UTF-8 BOM or a displayed warning
  4. "output started at file:line"
  5. output_buffering = 4096 can hide it

basics

~20 s

Headers are sent with the first byte of body output, so header() fails with that warning once anything has been output: an echo, whitespace outside the PHP tags, a BOM or a displayed warning. Send headers before any output.

solid answer

~50 s

HTTP puts every header before the body, so PHP sends the pending headers the moment the first byte of output leaves its buffers. After that, `header()`, `http_response_code()` and cookie functions cannot change anything: PHP emits an `E_WARNING`, "Cannot modify header information - headers already sent by (output started at /path/file.php:12)", and the script **carries on**, so a redirect silently never happens. The usual culprits are an `echo` or HTML before the call, a blank line before `<?php` or after a closing `?>` in an included file, a UTF-8 byte order mark, and a warning printed because `display_errors` is on. The message names the file and line where output began; `headers_sent($file, $line)` reports the same in code. Fix the cause: decide headers first, render afterwards, and omit the closing tag in pure-PHP files. The shipped `output_buffering = 4096` can mask the bug until early output grows past 4 KB.

code

php · 12 lines
php
<?php
declare(strict_types=1);

// Admin panel: save, then redirect - no output before this point
$saved = saveUser($_POST);            // application function

if (headers_sent($file, $line)) {
    throw new LogicException("Cannot redirect: output started at {$file}:{$line}");
}
header('Location: /admin/users', true, 303);
exit;
// no closing ?> tag in this file

go deeper

for a junior

Recall that headers must be sent before any output, and know the usual causes: echo, whitespace outside tags, a BOM and displayed warnings.

for a middle

Explain that header() only warns and the script continues, read the output-started location, and use headers_sent() to fail loudly.

for a senior

Recognise how output_buffering masks the bug, fix the ordering rather than the symptom, and keep display_errors off so warnings never become output.

for a principal

Set a structure where handlers return a response object and only one place emits headers and body, so this class of bug cannot occur.

## Why there is a deadline for headers An HTTP response is a status line, then headers, then the body. PHP collects the headers you set with `header()`, `http_response_code()` and the cookie functions in a list, and sends that list **once**, immediately before the first byte of body output reaches the server API. After that point the headers are on their way to the client and cannot be changed. "Output" means anything that reaches the output layer: `echo`, `print`, HTML outside `<?php ... ?>`, `var_dump()`, `printf()`, and PHP's own error messages when they are displayed. ## What the warning looks like and what it means Calling `header()` after output produces an `E_WARNING`: "Cannot modify header information - headers already sent by (output started at /srv/admin/src/bootstrap.php:1)" Three things to notice: - the **file and line in parentheses** are where output started, not where `header()` was called; - `header()` returns nothing and does **not** throw, so execution continues; - the header is simply dropped, so a redirect that "does nothing" is often this bug with `display_errors` off. `http_response_code()` reports the same condition with its own warning ("Cannot set response code - headers already sent") and returns `false`. ## The usual culprits | Cause | Where to look | |---|---| | `echo` or HTML before the header call | the controller or template order | | blank line or space before `<?php` | first line of the file named in the warning | | newline after a closing `?>` | the end of an included config or class file | | UTF-8 byte order mark (3 invisible bytes) | editor encoding of the named file | | a displayed notice or warning | any warning printed before the call while `display_errors` is on | | debugging output | a forgotten `var_dump()` | The line number is the fastest clue: line 1 of a file usually means a BOM or leading whitespace, and the last line means a stray newline after `?>`. ## Checking in code `headers_sent(&$filename = null, &$line = null): bool` returns `true` once headers have gone out and fills the two variables with the output start location. It is useful in a front controller to fail loudly ("cannot redirect, output started at ...") instead of silently. ## Diagnosing a redirect that silently does nothing When a save-then-redirect in an admin panel leaves the user on a blank or half-rendered page: 1. Look in the **error log** for "headers already sent"; with `display_errors` off it never appears on screen. 2. Read the **output started at** location, not the line of the `header()` call. 3. Open that file and check line 1 for a BOM or whitespace, the last line for text after `?>`, and the lines around it for `echo` or debug output. 4. If the location is a template, the handler rendered before deciding; move the redirect decision above the rendering. 5. Reproduce with `output_buffering` off, so the bug cannot hide behind a buffer. ## Fixing it properly 1. **Decide the response first, render second.** Do the work, choose the status, set headers or redirect, and only then produce HTML. 2. **Omit the closing `?>`** in files that contain only PHP; a trailing newline after it is output. 3. **Save files as UTF-8 without a BOM.** 4. **Keep `display_errors` off in production** and log instead, so a warning never becomes output. 5. **Remove debug output** before committing. ## Why output buffering hides the bug Both shipped `php.ini` files set `output_buffering = 4096`. With that, early output is held in a 4 KB buffer, and headers are not sent until the buffer is flushed. A handler that echoes a little text and then redirects "works" - until the early output grows past 4096 bytes, or the code runs under a configuration without the buffer (the compiled-in default is `0`, and the CLI forces it off). Relying on the buffer turns a clear error into an intermittent one; fix the order instead, and use explicit `ob_start()` only where capturing output is the actual goal.

  • The warning says output started at line 1 of config.php, which only defines constants; what is the likely cause?
    Something before `<?php` on line 1: a UTF-8 byte order mark saved by the editor, or a space or blank line. Both are sent as body output the moment the file is included. Re-save the file as UTF-8 without BOM and make `<?php` the very first bytes; if the warning named the file's last line, look for a newline after a closing `?>`.
  • Why does a redirect after a small echo work on one server and fail on another?
    Output buffering. With `output_buffering = 4096`, as in both shipped `php.ini` files, early output is held in a buffer and headers are still unsent when `header()` runs. On a setup with buffering off, the compiled-in default, the echo sends headers immediately and the redirect fails. The code has the bug on both; only one configuration hides it.

Headers are the envelope and output is the letter: once the first page is posted, you can no longer change the address. An output buffer is a tray on the desk that holds pages until it fills, which is why a small mistake slips through and a larger one does not.

saying these in an interview costs you the question

  • header() throws an exception when headers are already sent.
  • The warning's file and line show where header() was called.
  • Whitespace outside the PHP tags is ignored and never sent.
  • Turning on output_buffering is the proper fix.
  • A warning shown with display_errors on cannot trigger this error.