PHP: Was bedeutet "Cannot modify header information"?
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
Diese Meldung nennt dir die Ursache ziemlich genau. Man muss nur wissen, welche der beiden Zeilennummern darin die richtige ist — und die meisten lesen die falsche.
Warum das überhaupt passiert
HTTP schickt zuerst alle Header, dann eine Leerzeile, dann den Body. In dieser Reihenfolge, und sie lässt sich nicht umdrehen.
Sobald dein Skript also das erste Byte ausgibt, muss PHP die Header abschicken
— sonst käme der Body vor ihnen an. Jeder spätere header()-Aufruf kommt zu
spät.
“Ausgabe” ist dabei alles, was aus deinem Skript herausfällt. Auch ein einzelnes Leerzeichen.
Die Meldung enthält zwei Ortsangaben
Und das ist der Punkt, an dem die meisten Zeit verlieren:
headers already sent by (output started at DATEI:N) in DATEI on line M
N ist die Zeile, die die Ausgabe verursacht hat. M ist nur die Zeile,
in der der header()-Aufruf steht, der sich beschwert.
Wer bei M nachsieht, findet dort einen ganz normalen header()-Aufruf und
nichts Auffälliges. Die Ursache steht bei N.
Ich habe die vier häufigsten Fälle in einem Apache-Container nachgestellt. Hier sind sie mit den echten Zeilennummern.
Fall 1: Eine Leerzeile vor dem <?php
Die Datei beginnt mit einer leeren Zeile, dann kommt <?php.
(output started at /var/www/html/a-leerzeile-davor.php:1)
in /var/www/html/a-leerzeile-davor.php on line 3
Alles außerhalb von <?php ... ?> gibt PHP unverändert aus. Auch ein
Zeilenumbruch.
Fall 2: Whitespace nach dem ?>
Die Datei hat in Zeile 3 ein ?> und danach eine Leerzeile.
(output started at /var/www/html/b-nach-schliessendem-tag.php:4)
in /var/www/html/b-nach-schliessendem-tag.php on line 6
Das ist der Fall, den man am schwersten sieht, weil viele Editoren am Dateiende automatisch einen Zeilenumbruch anhängen — und das ist auch richtig so, es gehört zur Definition einer Textzeile.
Die Lösung ist einfach: In Dateien, die nur PHP enthalten, lässt man das
schließende ?> weg. Das ist keine Marotte, sondern die offizielle
Empfehlung im PHP-Handbuch und in PSR-12. Wenn kein ?> da ist, kann danach
auch nichts stehen.
Fall 3: Ein echo vor dem header()
(output started at /var/www/html/c-echo-davor.php:2)
in /var/www/html/c-echo-davor.php on line 3
Der offensichtliche Fall. Häufig ist es kein echo, sondern ein vergessenes
var_dump() aus der Fehlersuche — oder eine PHP-Warnung, die selbst schon
ausgegeben wurde und damit die Header abgeschickt hat.
Der zweite Punkt ist wichtig: Wenn display_errors an ist, ist jede
Warnung eine Ausgabe. Ein “Undefined array key” weiter oben in deinem Skript
kann also der Grund dafür sein, dass weiter unten die Weiterleitung nicht
funktioniert.
Fall 4: Der BOM
(output started at /var/www/html/d-bom.php:1)
in /var/www/html/d-bom.php on line 2
Der Byte Order Mark ist eine unsichtbare Bytefolge (EF BB BF) am
Dateianfang, die manche Editoren beim Speichern als UTF-8 hinzufügen. Für PHP
ist er Ausgabe wie jede andere.
Das ist der Fall, der am längsten Rätsel aufgibt: Die Meldung zeigt auf Zeile
1, und in Zeile 1 steht sichtbar <?php. Im Body der Antwort sieht man den
BOM dann als merkwürdiges Zeichen vor der eigentlichen Ausgabe.
Prüfen kannst du das so:
user@pc:~$ head -c 3 datei.php | xxd
00000000: efbb bf ...
Wenn dort efbbbf steht, hast du einen BOM. Die meisten Editoren haben eine
Einstellung “UTF-8 ohne BOM” — die ist für PHP-Dateien die richtige.
Wie du die Ursache findest
Der schnellste Weg ist headers_sent() mit zwei Parametern:
<?php
if (headers_sent($datei, $zeile)) {
echo "Ausgabe begann in $datei, Zeile $zeile";
}
?>
Ausgabe hier in Zeile 2
headers_sent() : true
Datei : f-headers-sent.php
Zeile : 2
Damit bekommst du den Verursacher genannt, nicht den Beschwerdeführer. Das
hilft besonders dann, wenn viele Dateien per include zusammenkommen und die
Ausgabe aus einer ganz anderen stammt als der, in der du gerade bist.
Der Grund, warum es “nur auf dem Server” auftritt
Jetzt der Teil, der die Sache oft so verwirrend macht. PHP hat eine
Einstellung namens output_buffering. Ist sie aktiv, sammelt PHP die Ausgabe
erst einmal in einem Puffer, und die Header können auch noch nachträglich
gesetzt werden.
Viele Distributionen und Docker-Images setzen einen Puffer von 4096 Byte. Auf so einem System tritt der Fehler bei kleinen Ausgaben gar nicht auf — bis die Ausgabe größer wird als der Puffer oder du auf ein System wechselst, das keinen hat.
Deshalb: Wenn der Fehler nur auf einem von zwei Systemen auftritt, vergleich zuerst
user@pc:~$ php -i | grep output_buffering
Für den Testaufbau zu diesem Artikel musste ich output_buffering = Off
setzen, sonst hätte sich keiner der vier Fälle gezeigt.
Output Buffering als Notlösung
Man kann sich das auch zunutze machen:
<?php
ob_start();
echo "Diese Ausgabe wird gepuffert\n";
header('X-Test: sieben');
echo "und der Header geht trotzdem durch\n";
ob_end_flush();
?>
H| X-Test: sieben
B| Diese Ausgabe wird gepuffert
B| und der Header geht trotzdem durch
Der Header kommt an, obwohl vorher echo stand.
Ich halte das für eine Notlösung und nicht für eine Lösung. Es versteckt das eigentliche Problem — dass dein Code Ausgabe erzeugt, bevor er weiß, was er ausgeben will. Und es kostet Speicher, weil die gesamte Antwort erst gesammelt wird.
Der saubere Weg ist, die Struktur zu ändern: erst rechnen und entscheiden,
dann ausgeben. Alle header()-Aufrufe gehören an den Anfang, vor die erste
Zeile Ausgabe.
Und wie es richtig aussieht
<?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
Zusammenfassung
- Die Meldung nennt zwei Orte.
output started at DATEI:Nist die Ursache,on line Mnur die Beschwerde. headers_sent($datei, $zeile)liefert dir den Verursacher direkt.- Das schließende
?>in reinen PHP-Dateien weglassen — dann kann danach nichts mehr stehen. - Dateien ohne BOM speichern; prüfen mit
head -c 3 datei.php | xxd. - Bei aktivem
display_errorsist jede Warnung eine Ausgabe. - Tritt der Fehler nur auf einem System auf, vergleich
output_buffering. ob_start()verdeckt das Problem, es löst es nicht.
Hinweis zu Netcup (Werbung)
Der deutsche Hoster Netcup bietet unter anderem günstige und zugleich leistungsstarke Webhosting Pakete, KVM-basierte Root Server und dezidierte Server an. Mit unseren Gutscheincodes kannst du noch mehr Geld sparen (6€ bei deiner ersten Bestellung, 30% Rabatt auf alle KVM-basierten Root Server, ...).