PHP: What does "Cannot modify header information" mean?
Warning: Cannot modify header information - headers already sent by
(output started at /var/www/html/datei.php:1) in /var/www/html/datei.php on line 3
This message tells you the cause quite precisely. You just have to know which of the two line numbers in it is the right one — and most people read the wrong one.
Why this happens at all
HTTP sends all the headers first, then a blank line, then the body. In that order, and it cannot be turned around.
So as soon as your script emits its first byte, PHP has to send the headers —
otherwise the body would arrive before them. Every later header() call comes
too late.
“Output” here means anything that falls out of your script. Including a single space.
The message contains two locations
And this is where most people lose time:
headers already sent by (output started at FILE:N) in FILE on line M
N is the line that caused the output. M is merely the line containing
the header() call that is complaining.
Anyone looking at M finds a perfectly ordinary header() call there and
nothing suspicious. The cause is at N.
I reproduced the four most common cases in an Apache container. Here they are with the actual line numbers.
Case 1: a blank line before the <?php
The file begins with an empty line, then comes <?php.
(output started at /var/www/html/a-leerzeile-davor.php:1)
in /var/www/html/a-leerzeile-davor.php on line 3
Everything outside <?php ... ?> is output by PHP unchanged. Including a line
break.
Case 2: whitespace after the ?>
The file has a ?> on line 3 and a blank line after it.
(output started at /var/www/html/b-nach-schliessendem-tag.php:4)
in /var/www/html/b-nach-schliessendem-tag.php on line 6
This is the hardest one to see, because many editors automatically append a line break at the end of a file — and rightly so, that is part of the definition of a text line.
The fix is simple: in files containing only PHP, leave the closing ?>
out. That is not a quirk but the official recommendation in the PHP manual
and in PSR-12. If there is no ?>, nothing can come after it.
Case 3: an echo before the header()
(output started at /var/www/html/c-echo-davor.php:2)
in /var/www/html/c-echo-davor.php on line 3
The obvious case. Often it is not an echo but a forgotten var_dump() from
debugging — or a PHP warning that has itself been output and thereby sent the
headers.
The second point matters: when display_errors is on, every warning is
output. So an “Undefined array key” further up in your script can be the
reason a redirect further down does not work.
Case 4: the BOM
(output started at /var/www/html/d-bom.php:1)
in /var/www/html/d-bom.php on line 2
The byte order mark is an invisible byte sequence (EF BB BF) at the start of
a file which some editors add when saving as UTF-8. To PHP it is output like
any other.
This is the case that puzzles people longest: the message points at line 1, and
line 1 visibly contains <?php. In the response body you then see the BOM as a
strange character before the actual output.
You can check for it like this:
user@pc:~$ head -c 3 datei.php | xxd
00000000: efbb bf ...
If that reads efbbbf you have a BOM. Most editors have a “UTF-8 without BOM”
setting — that is the right one for PHP files.
How to find the cause
The quickest route is headers_sent() with two parameters:
<?php
if (headers_sent($file, $line)) {
echo "output began in $file, line $line";
}
?>
Ausgabe hier in Zeile 2
headers_sent() : true
Datei : f-headers-sent.php
Zeile : 2
That names the culprit rather than the complainant. It helps particularly when
many files come together via include and the output originates in a
completely different one from the one you are in.
The reason it happens “only on the server”
Now for the part that makes this so confusing. PHP has a setting called
output_buffering. When it is active, PHP first collects the output in a
buffer, and the headers can still be set afterwards.
Many distributions and Docker images set a buffer of 4096 bytes. On such a system the error does not occur at all for small outputs — until the output grows beyond the buffer or you move to a system that does not have one.
Hence: if the error only occurs on one of two systems, first compare
user@pc:~$ php -i | grep output_buffering
For the test setup behind this article I had to set output_buffering = Off,
otherwise none of the four cases would have shown up.
Output buffering as a stopgap
You can also turn this to your advantage:
<?php
ob_start();
echo "This output is buffered\n";
header('X-Test: sieben');
echo "and the header gets through anyway\n";
ob_end_flush();
?>
H| X-Test: sieben
B| Diese Ausgabe wird gepuffert
B| und der Header geht trotzdem durch
The header arrives, even though there was an echo before it.
I consider that a stopgap rather than a solution. It hides the actual problem — that your code produces output before it knows what it wants to output. And it costs memory, because the whole response is collected first.
The clean route is to change the structure: calculate and decide first, then
output. All header() calls belong at the top, before the first line of
output.
And how it looks when done right
<?php
header('X-Test: fuenf');
header('Content-Type: text/plain');
echo "Ausgabe\n";
?>
H| HTTP/1.1 200 OK
H| X-Test: fuenf
H| Content-Type: text/plain;charset=UTF-8
B| Ausgabe
Summary
- The message names two locations.
output started at FILE:Nis the cause,on line Mmerely the complaint. headers_sent($file, $line)gives you the culprit directly.- Leave the closing
?>out of pure PHP files — then nothing can follow it. - Save files without a BOM; check with
head -c 3 datei.php | xxd. - With
display_errorson, every warning is output. - If the error only occurs on one system, compare
output_buffering. ob_start()masks the problem, it does not solve it.
About Netcup (advertisement)
The German host Netcup offers, among other things, affordable and powerful web hosting packages, KVM-based root servers and dedicated servers. With our voucher codes you can save even more (6€ off your first order, 30% off all KVM-based root servers, ...).