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
<?phpor 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
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:
- PHP starts running the script.
require 'init.php';includes another file.- If
init.phpprints anything, output may already begin here. echo "Loading...";definitely sends output to the browser.header('Location: /home.php');now tries to send a redirect header.- PHP detects that output has already started.
- PHP raises the
Cannot modify header information - headers already sentwarning. - 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:
session_start()sends session headers.setcookie()sends a cookie header.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.
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
| Concept | What it does | Must happen before output? | Notes |
|---|---|---|---|
header() | Sends raw HTTP headers | Yes | Used for redirects, content type, cache control |
setcookie() | Sends a Set-Cookie header | Yes | Cookies are part of headers |
session_start() | Starts session handling and usually sends a cookie | Yes | Often fails if output started already |
echo / print | Sends response body content | No | But once used, headers are usually locked |
| HTML outside PHP | Sends response body content | No | Can trigger the warning before header calls |
ob_start() | Buffers output temporarily | Not a header itself | Can help, but should not replace good structure |
header() vs echo
header()changes response metadata.echosends visible content.- Metadata must go first.
Fixing the cause vs masking it
| Approach | Recommended? | Why |
|---|---|---|
Move header() before output | Yes | Correct and clean |
| Remove accidental whitespace/BOM | Yes | Fixes hidden output |
| Restructure includes/templates | Yes | Prevents repeat issues |
Use ob_start() everywhere | Sometimes | Useful, but can hide design problems |
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
echobefore 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.
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." ]
Keep learning
Related questions
Are PDO Prepared Statements Enough to Prevent SQL Injection in PHP?
Learn how PDO prepared statements prevent SQL injection in PHP, what they protect, and the mistakes that still leave MySQL apps vulnerable.
Can You Bind an Array to an IN Clause in PHP PDO?
Learn how PDO handles placeholders in IN() clauses, why arrays cannot be bound directly, and the safe PHP pattern to build dynamic queries.
Choosing the Right MySQL Collation for PHP and UTF-8
Learn how MySQL character sets and collations work with PHP, and how to choose a practical UTF-8 setup for web applications.