How to Fix the “Headers Already Sent” Error in PHP
When you work with PHP redirects, cookies, or sessions, you may face this common warning:
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 happens when your code calls header() or setcookie() after PHP has already sent output (HTML/text) to the browser.
In this blog post, you’ll learn:
- What “headers already sent” really means
- The real reasons it happens
- How to fix it correctly (with clean coding examples)
- Best practices to avoid it forever
What Are Headers in PHP?
When a browser requests a page, the server responds in two parts:
- Headers (metadata):
- Redirect location
- Cookies
- Content type (HTML/JSON)
- Cache rules
- Body (actual output):
- HTML
- Text
- JSON
PHP must send headers before the body.
So, if any output (even a space) is sent first, then later you try:
header("Location: ...")setcookie(...)session_start()
PHP throws the warning because headers are already locked.
Why This Error Happens (Most Common Reasons)
You Printed Output Before header() or setcookie():
Wrong:
<?php
echo "Hello"; // Output starts here
header("Location: dashboard.php"); // Now it's too late
Correct:
<?php
header("Location: dashboard.php");
exit;
Always use exit; after redirects to stop script execution.
Whitespace or Blank Line Before <?php:
Even one empty line before <?php counts as output.
Wrong (notice blank line at top):
<?php
header("Location: home.php");
Correct:
<?php
header("Location: home.php");
exit;
Closing ?> Tag in PHP-Only Files:
In pure PHP files (like config.php, functions.php) the closing ?> can accidentally output whitespace.
Risky:
<?php
// config.php
$siteName = "My Site";
?>
Best practice (remove ?>):
<?php
$siteName = "My Site";
Output from Included Files (include / require):
If an included file prints HTML before headers, your headers will fail.
Example:
<?php
require "header.php"; // outputs HTML
setcookie("user", "Ali", time() + 3600); // fails
Fix: Set cookies before loading templates:
<?php
setcookie("user", "Ali", time() + 3600, "/");
require "header.php";
UTF-8 BOM (Invisible Characters):
Some editors save files with a BOM (Byte Order Mark). Those invisible bytes are output before PHP runs.
Fix:
- Re-save your file as UTF-8 without BOM
- In VS Code: “Save with Encoding” → “UTF-8”
How to Find the Exact Source of the Problem
Read this part carefully:
output started at /some/file.php:12
This means output began at file.php line 12.
That is the real place to check, not only the line where header() fails.
At that line, look for:
echo,print- HTML outside PHP
- spaces/blank lines
- closing
?> - includes that output content
Correct Fix Patterns (With Proper Coding)
Redirect Before Any Output:
<?php
// redirect.php
if (!isset($_GET['ok'])) {
header("Location: login.php");
exit;
}echo "Welcome!"; // This is safe now because redirect already happened (or didn’t)
Correct Use of Cookies:
<?php
// Must be before any output
setcookie("theme", "dark", time() + 86400, "/");echo "Cookie is set!";
Correct Use of Sessions:
<?php
session_start(); // must be first$_SESSION['user'] = "Ali";header("Location: profile.php");
exit;
Fix Using Output Buffering (Useful for Old Projects)
If your project is large and it’s hard to control output order, use output buffering.
<?php
ob_start(); // start bufferingecho "<h1>Loading...</h1>"; // output buffered, not sent yetheader("Location: home.php");
exit;ob_end_flush();
Note: Buffering helps, but best practice is still to structure code correctly.
Best Practices to Avoid This Error Forever
Do these every time:
- Put
session_start(),setcookie(), andheader()at the top - Never output HTML before redirects/cookies
- Remove closing
?>from PHP-only files - Keep config/includes “silent” (no echo, no HTML)
- Use
exit;afterheader("Location: ...")
Full Example: Clean Login Redirect Script
<?php
session_start();// If user is not logged in, redirect
if (!isset($_SESSION['user_id'])) {
header("Location: login.php");
exit;
}// Set a cookie safely
setcookie("visited", "yes", time() + 3600, "/");
?>
<!doctype html>
<html>
<head>
<title>Dashboard</title>
</head>
<body>
<h1>Welcome to Dashboard</h1>
</body>
</html>
This is a correct structure:
headers/cookies first → HTML after
Conclusion
The “Cannot modify header information headers already sent” warning happens because PHP must send headers before output, and your script is outputting something earlier (even invisible whitespace or BOM). The correct fix is to move header/cookie/session calls before output and clean up included files to avoid accidental output.