"Headers already sent" is one of the most common PHP warnings, and it breaks sessions, logins and redirects. It always means the same thing: your script sent some output to the browser before it tried to send a header. The warning tells you exactly where that output started, so the fix is usually a one-line change once you know how to read it.
PHP must send headers (cookies, redirects, session_start()) before any output. Read the "output started at file:line" part of the warning, go to that file and line, and remove what is printed there: HTML before <?php, a blank line or space outside the PHP tags, an echo, or an invisible UTF-8 byte order mark. Leave off the closing ?> in PHP-only files. Output buffering can hide the problem, but fix the stray output first.
1. Read the warning: it tells you where to look
The session warnings look like these in PHP 8:
Warning: session_start(): Session cannot be started after headers have already been sent in /home/user/public_html/login.php on line 12
Warning: Cannot modify header information - headers already sent by (output started at /home/user/public_html/config.php:1) in /home/user/public_html/login.php on line 20There are two locations, and they mean different things:
| Part of the message | What it is | What to do |
|---|---|---|
| "in login.php on line 20" | The header call that failed: session_start(), header() or setcookie() | Usually nothing is wrong here |
| "output started at config.php:1" | The place where output first went to the browser | Go here and remove the output |
If the message only names one file, look at the lines above the failing call in that file, and at every file it includes before that point.
2. Why the error happens
An HTTP response is headers first, then the page. The moment PHP prints a single character, even a space, it sends the headers and starts on the page. After that, nothing can add a cookie or a redirect. session_start() needs to send the session cookie, header('Location: …') sends a redirect, and setcookie() sends a cookie, so all three fail once output has begun.
3. The usual causes and fixes
| Cause | What it looks like | Fix |
|---|---|---|
| HTML before the PHP code | The page starts with DOCTYPE or a blank line, then the opening PHP tag | Move the PHP block with session_start() to the very top |
| An echo or print before the header | Debug output, a welcome message or var_dump() above session_start() | Move the header call first, or remove the output |
| Whitespace after a closing tag | A blank line after ?> at the end of an included file | Delete the closing ?> in PHP-only files |
| A byte order mark (BOM) | "output started at …:1" and nothing visible on line 1 | Re-save the file as UTF-8 without BOM |
| A PHP notice or warning printed on screen | An earlier warning counts as output | Fix that warning, and log errors instead of displaying them |
The correct shape of a page that uses sessions:
<?php
session_start();
if (!isset($_SESSION['user_id'])) {
header('Location: /login.php');
exit;
}
?>
<!DOCTYPE html>
<html lang="en">
<body>
<p>Welcome back.</p>
</body>
</html>The session and any redirect come first; HTML follows. Always call exit after a Location header so the rest of the script does not run.
4. The invisible culprit: a byte order mark
Some editors, notably older Windows ones, save UTF-8 files with three invisible bytes at the start, called a byte order mark. PHP prints them before your opening tag, so the warning points at line 1 of a file that looks perfectly clean.
To fix it, open the file in an editor such as VS Code or Notepad++, change the encoding to UTF-8 (not "UTF-8 with BOM") and save. From a terminal, you can find affected files with:
grep -rlI $'^\xEF\xBB\xBF' --include='*.php' .5. Drop the closing tag in PHP-only files
A file that contains only PHP, such as config.php or a class file, does not need the closing ?>. Leaving it off means no stray newline after it can ever be sent. The PSR-12 coding standard requires this, and most frameworks follow it.
<?php
// config.php: no closing tag at the end of this file
const DB_HOST = 'localhost';6. Output buffering: a safety net, not a fix
Output buffering collects output in memory instead of sending it at once, so headers can still be sent later:
<?php
ob_start();
echo 'This is held in the buffer';
session_start(); // works, because nothing has been sent yet
ob_end_flush();It is useful for templates that genuinely need to send a header late, but using it to silence the warning hides the real problem, which often returns when a file is included in a different order. Find and remove the stray output first. Buffering can also be turned on for a whole site with output_buffering in a .user.ini file.
7. See the errors without showing them to visitors
While you debug on a development copy, you can show errors on screen:
<?php
ini_set('display_errors', '1');
error_reporting(E_ALL);Do not leave this on a live site. Displayed errors reveal file paths and details to visitors, and the error text itself is output, which causes more "headers already sent" warnings. On a live site, keep display_errors off and read the error log instead.
8. Other session errors you may see
- "Session ini settings cannot be changed when a session is active": you called
ini_set('session.…')aftersession_start(). Move the setting above it. - "Session cannot be started after headers have already been sent": the same stray-output problem as above.
- "Ignoring session_start() because a session is already active":
session_start()is called twice, often in an included file. Guard it withif (session_status() === PHP_SESSION_NONE) { session_start(); }. - Session data lost between pages: usually a cookie problem, such as switching between
wwwand non-www, or aSecurecookie on anhttp://page. See How to use PHP sessions in cPanel.
For session security and design, see Master PHP sessions with ease.
9. On Domain India hosting
On our cPanel servers, measured on 23 September 2026, PHP 8.3 ships with output_buffering off and display_errors off, and it logs errors to a file named error_log, which usually appears in the folder of the script that raised the error. So a stray space shows up as a warning in that log rather than on the page. Reviewing error logs in cPanel and DirectAdmin shows where to find them, and How to enable the display_errors option covers switching display on for testing. Change PHP settings with a .user.ini file or cPanel's MultiPHP INI Editor; a php_value line in .htaccess causes a 500 error on our servers.
- 25 GB NVMe SSD Storage
- 50 GB Monthly Bandwidth
- 1 Website
- 10 Email Accounts
The card shows the live price, excluding 18% GST.
Frequently asked questions
What does "headers already sent" mean in PHP?
Your script printed some output, even a single space, before calling a function that sends an HTTP header, such as session_start(), header() or setcookie(). Once output starts, headers can no longer be sent. The warning's "output started at" part shows the file and line where the output began.
How do I fix "session_start(): Session cannot be started after headers have already been sent"?
Move session_start() to the very top of the script, before any HTML, blank lines or echo statements, and check every file included before it for stray output. Remove the closing ?> from PHP-only files and re-save files as UTF-8 without a byte order mark.
The warning points at line 1 but nothing is there. Why?
The file almost certainly starts with an invisible UTF-8 byte order mark, or has a space or blank line before the opening PHP tag. Re-save it as UTF-8 without BOM in your editor and remove anything before the opening tag.
Should I use ob_start() to fix header errors?
Only as a last resort. Output buffering hides the warning by holding output back, but the stray output is still there and the problem can return when files are included in another order. Find and remove the output first.
Should display_errors be on for my live website?
No. Keep it off on a live site and read the error log instead. Displayed errors reveal file paths to visitors and count as output, which can cause more headers already sent warnings.
Ready to track down the cause? Open your hosting services to reach cPanel and its error logs, and read Master PHP sessions with ease once the warning is gone.
Send us the page address and the full warning, and we will check the PHP settings and error log for your account with you.
Open a support ticket