Question

How to Fix the "Headers Already Sent" Error in PHP

phphttp-headers

Question

I am running a PHP script and getting warnings like this:

Warning: Cannot modify header information - headers already sent by (output started at /some/file.php:12) in /some/file.php on line 23

The lines mentioned in the warning contain calls such as header() and setcookie().

What causes this error in PHP, and how can it be fixed correctly?

Short Answer

By the end of this page, you will understand what HTTP headers are, why PHP must send them before any body output, what triggers the Headers already sent warning, and how to fix it in practical ways. You will also see common causes such as accidental whitespace, echo output, included files, UTF-8 BOM issues, and debugging prints before header() or setcookie().

Concept

In PHP, functions like header(), setcookie(), and session_start() work by sending HTTP headers to the browser. Headers are metadata sent before the actual page content.

Once PHP has started sending output to the browser—such as HTML, text, whitespace, or anything printed with echo—the headers are considered sent. After that point, PHP can no longer change them, so you get this warning:

Cannot modify header information - headers already sent

Why this matters

Headers control important behavior, such as:

  • redirects with header('Location: ...')
  • cookies with setcookie()
  • sessions with session_start()
  • content type with header('Content-Type: application/json')
  • cache settings and download responses

If output happens too early, PHP loses the ability to send those instructions properly.

What counts as output?

Any of the following can trigger the problem:

  • echo, print, var_dump()
  • raw HTML outside <?php ... ?>
  • blank lines before <?php or after ?>
  • whitespace in included files
  • UTF-8 BOM at the start of a file
  • error messages or warnings printed before header calls

Reading the warning

Example:

Warning: Cannot modify header information - headers already sent by (output started at /some/file.php:12) in /some/file.php on line 23

This usually means:

  • output started at /some/file.php:12
  • PHP later tried to send a header at line 23

So line 23 is often not the real cause. The real cause is usually the first output at or before line 12.

Mental Model

Think of an HTTP response like mailing a package:

  • Headers are the shipping label and instructions.
  • Output is the actual item inside the box.

Once you seal and ship the package, you cannot go back and change the shipping label. In the same way, once PHP starts sending page content, it cannot change headers anymore.

So the rule is simple:

  • send all header-related instructions first
  • send page content after that
Take Quiz

Syntax and Examples

The most common header-related functions are:

header('Location: /login.php');
setcookie('theme', 'dark', time() + 3600);
session_start();

These must happen before output.

Broken example

<?php
echo "Welcome";
header('Location: /dashboard.php');

This fails because echo "Welcome"; sends output before the redirect header.

Correct example

<?php
header('Location: /dashboard.php');
exit;

Here, the header is sent before any output.

Another broken example with HTML

<html>
<body>
<?php
setcookie('user', 'alice', time() + 3600);
?>
</body>
</html>

This may fail because the HTML before setcookie() is output first.

Correct version

<?php
setcookie('user', 'alice', time() + 3600);
?>
<html>
<body>
<p>Cookie set.</p>
</body>
</html>

Included file problem

config.php

 
<?php
$dbHost = 'localhost';

index.php

<?php
require 'config.php';
header('Location: /home.php');
exit;

If config.php contains leading whitespace before <?php, that whitespace may be sent as output. Removing it fixes the issue.

Best practice

For files that contain only PHP, omit the closing ?> tag:

<?php
return [
    'host' => 'localhost',
];

This helps avoid accidental trailing whitespace.

Step by Step Execution

Consider this code:

<?php
require 'init.php';
echo "Loading...";
header('Location: /home.php');
exit;

Now trace it step by step:

  1. PHP starts running the script.
  2. require 'init.php'; includes another file.
  3. If init.php prints anything, output may already begin here.
  4. echo "Loading..."; definitely sends output to the browser.
  5. header('Location: /home.php'); now tries to send a redirect header.
  6. PHP detects that output has already started.
  7. PHP raises the Cannot modify header information - headers already sent warning.
  8. The redirect may fail or behave unexpectedly.

Fixed version

<?php
require 'init.php';
header('Location: /home.php');
exit;

Another useful trace

<?php
session_start();
setcookie('mode', 'dark');
echo "Done";

This works because:

  1. session_start() sends session headers.
  2. setcookie() sends a cookie header.
  3. echo "Done"; sends body output after headers.

That order is valid.

Real World Use Cases

This concept appears often in real PHP applications.

Redirecting after login

<?php
if ($loginSuccessful) {
    header('Location: /dashboard.php');
    exit;
}

If any debug output appears before this, the redirect can fail.

Setting cookies for user preferences

<?php
setcookie('language', 'en', time() + 86400 * 30);

This must happen before HTML output.

Starting sessions

<?php
session_start();
$_SESSION['user_id'] = 42;

If output starts too early, session cookies may not be sent.

Serving JSON from an API endpoint

<?php
header('Content-Type: application/json');
echo json_encode(['status' => 'ok']);

The content type header must be sent before the JSON output.

File downloads

<?php
header('Content-Type: application/pdf');
header('Content-Disposition: attachment; filename="report.pdf"');
readfile('report.pdf');

Any output before these headers can corrupt the download response.

Take Quiz

Real Codebase Usage

In real projects, developers avoid this error by structuring code so response decisions happen early.

Common patterns

Guard clauses and early redirects

<?php
session_start();

if (!isset($_SESSION['user_id'])) {
    header('Location: /login.php');
    exit;
}

Check conditions early, then redirect before rendering anything.

Separate logic from templates

A common structure is:

  • bootstrap/config loads first
  • request handling and validation run next
  • headers, redirects, or cookies are sent
  • HTML template is rendered last

Validation before output

<?php
$token = $_GET['token'] ?? '';

if ($token === '') {
    header('Location: /error.php');
    exit;
}

Avoid debug output in production flow

Debugging with var_dump() before redirects is a common cause of this error.

Use output buffering only when appropriate

PHP supports output buffering:

<?php
ob_start();
echo "Hello";
header('X-Debug: test');
ob_end_flush();

This can delay output so headers still work, but it should not be used to hide poor structure. It is better to fix the actual order of operations when possible.

Omit closing PHP tags in pure PHP files

This reduces accidental whitespace output in config files, class files, and included scripts.

Common Mistakes

1. Printing before calling header()

Broken:

<?php
echo "Redirecting...";
header('Location: /home.php');

Fix:

<?php
header('Location: /home.php');
exit;

2. Whitespace before <?php

Broken:

 
<?php
header('Location: /home.php');

That blank space may count as output.

Fix: remove all characters before the opening <?php tag.

3. Trailing whitespace after ?>

Broken:

<?php
$config = ['debug' => true];
?> 

If this file is included, trailing spaces may cause output.

Fix: omit the closing tag in pure PHP files.

<?php
$config = ['debug' => true];

4. Hidden BOM characters

Some editors save files with a UTF-8 BOM at the beginning. PHP may treat that as output.

Fix:

  • save files as UTF-8 without BOM
  • check your editor encoding settings

5. Errors printed before headers

Broken:

<?php
include 'missing-file.php';
header('Location: /home.php');

If the include triggers a warning, that warning output may send content first.

Fix:

  • resolve warnings and notices
  • check included files carefully
  • inspect the file and line shown in the warning

6. Forgetting exit after redirect

Not the same error, but still a common issue:

<?php
header('Location: /home.php');
// script may continue running

Better:

<?php
header('Location: /home.php');
exit;

Comparisons

ConceptWhat it doesMust happen before output?Notes
header()Sends raw HTTP headersYesUsed for redirects, content type, cache control
setcookie()Sends a Set-Cookie headerYesCookies are part of headers
session_start()Starts session handling and usually sends a cookieYesOften fails if output started already
echo / printSends response body contentNoBut once used, headers are usually locked
HTML outside PHPSends response body contentNoCan trigger the warning before header calls
ob_start()Buffers output temporarilyNot a header itselfCan help, but should not replace good structure

header() vs echo

  • header() changes response metadata.
  • echo sends visible content.
  • Metadata must go first.

Fixing the cause vs masking it

ApproachRecommended?Why
Move header() before outputYesCorrect and clean
Remove accidental whitespace/BOMYesFixes hidden output
Restructure includes/templatesYesPrevents repeat issues
Use ob_start() everywhereSometimesUseful, but can hide design problems
Take Quiz

Cheat Sheet

Core rule

Call these before any output:

  • header()
  • setcookie()
  • session_start()

Common causes of Headers already sent

  • echo, print, var_dump() before headers
  • HTML before PHP header calls
  • whitespace before <?php
  • trailing spaces after ?>
  • included files with accidental output
  • UTF-8 BOM
  • warnings/notices printed first

Safe redirect pattern

<?php
header('Location: /target.php');
exit;

Safe JSON response pattern

<?php
header('Content-Type: application/json');
echo json_encode(['ok' => true]);

Safe session pattern

<?php
session_start();

Place it at the top of the script.

Good habits

  • Put request handling before template output
  • Omit ?> in pure PHP files
  • Save files as UTF-8 without BOM
  • Do not debug with echo before redirects
  • Read both file locations in the warning message

Debug tip

If you see:

output started at /path/file.php:12

look there first. That is often the real source of the problem, not the line containing header().

FAQ

Why does PHP say headers were already sent?

Because some output reached the browser before PHP tried to send a header such as a redirect, cookie, or session header.

Does whitespace really count as output in PHP?

Yes. Spaces, blank lines, or characters outside PHP tags can be sent as output.

Why does the warning mention two different line numbers?

One line shows where output started. The other shows where PHP later tried to send the header.

Can setcookie() cause the same error as header()?

Yes. Cookies are sent through HTTP headers, so they must also be set before output.

Does session_start() need to be called before output?

Yes. It often sends a session cookie header, so it must run before body output.

Can I use output buffering to fix this?

Sometimes. ob_start() can delay output, but it is usually better to remove the early output and structure the script correctly.

Should I close PHP files with ?>?

In pure PHP files, it is usually better not to. This helps prevent accidental trailing whitespace.

Can warnings or notices trigger this problem?

Yes. If errors are displayed to the browser before a header call, they can count as output.

Take Quiz

Mini Project

Description

Build a small login gate script that checks whether a user is logged in. If not, it redirects to a login page. This project demonstrates the correct order of session handling, header sending, and output rendering in a practical PHP flow.

Goal

Create a PHP page that safely redirects unauthenticated users before any output is sent.

Requirements

[ "Start the session at the top of the script.", "Check whether a user ID exists in the session.", "Redirect to /login.php if the user is not logged in.", "Stop script execution after the redirect.", "Only render HTML if the user is authenticated." ]

Take Quiz

Keep learning

Related questions

View more