You open your cpanel" class="auto-link">WordPress site and instead of a page you get a wall of text: Warning: Cannot modify header information - headers already sent by (output started at /home/user/public_html/wp-config.php:42). Sometimes the site still loads but the admin login loops, or a redirect silently fails. It looks scary, but it's one of the more predictable PHP errors, and the error message itself tells you where to look.
Symptom: what you'll actually see
- A yellow or red PHP warning at the very top of the page, usually naming a file and a line number.
- Login to
/wp-adminfails or bounces back to the login form. - Redirects (WooCommerce checkout, plugin settings pages) stop working.
- Downloads or RSS feeds come out corrupted or fail with an XML parsing error.
Cause: PHP can only send headers once
Every web response has two parts: HTTP headers first, then the body. Headers carry things like cookies (Set-Cookie) and redirects (Location). The moment PHP outputs even one character of body, the headers are locked in. If any code afterwards calls header(), setcookie(), or wp_redirect(), PHP complains.
The part in brackets, output started at file:line, is the clue. That's the file that sent the first byte. Not the file that raised the warning. People often debug the wrong file because of this.
The usual suspects
| Culprit | How it sneaks in |
|---|---|
Whitespace before <?php | A blank line or space at the top of wp-config.php, functions.php or a plugin file |
Whitespace after ?> | A trailing newline after the closing tag at the bottom of a file |
| UTF-8 BOM | An editor saved the file "with BOM", adding invisible bytes at the start |
| Debug output | echo, var_dump or print_r left in a theme or plugin |
| PHP notices/warnings | An earlier warning prints text, which then breaks headers for everything after it |
| Pasted snippets | Code copied from a blog with smart characters or leading spaces |
Fix, step by step
1. Read the "output started at" path
Say it reads output started at /home/user/public_html/wp-content/themes/mytheme/functions.php:1. Line 1 almost always means whitespace or a BOM before <?php. Open that file in File Manager (cPanel → File Manager → Edit) or over SSH.
2. Remove whitespace and closing tags
Make sure <?php is the very first thing in the file, with no blank line above it. For pure-PHP files, delete the closing ?> at the end. PHP doesn't need it, and it removes the trailing-newline trap entirely.
3. Check for a BOM
If the file looks clean but the error persists on line 1, there's probably a BOM. Over SSH:
head -c 3 wp-config.php | xxd
# BOM shows as: efbb bf
To strip it from one file:
sed -i '1s/^\xEF\xBB\xBF//' wp-config.php
Or re-save the file in your editor as "UTF-8 without BOM". Make a backup copy before touching wp-config.php.
4. Find files with a BOM in bulk
cd ~/public_html
grep -rl $'\xEF\xBB\xBF' --include=*.php wp-content/themes wp-content/plugins
Anything this lists is a candidate. Don't fix core files by hand in wp-admin or wp-includes. If one shows up there, reinstall WordPress core instead (see below).
5. Rule out a plugin or theme
If the path points into wp-content/plugins/some-plugin/, rename that plugin's folder via File Manager to disable it:
mv wp-content/plugins/some-plugin wp-content/plugins/some-plugin.off
Reload the site. If the error disappears, update the plugin, contact its developer, or replace it. If it points to your theme, switch temporarily to a default theme like Twenty Twenty-Five by renaming your active theme's folder.
6. Hunt for early warnings
Sometimes the "output" is another PHP warning, not whitespace. Scroll to the very first message on the page. If the first message is, say, Deprecated: Function create_function(), fix that one, because the headers warning is only a side effect. Check your error log too: cPanel → Metrics → Errors, or ~/public_html/error_log.
7. Replace corrupted core files
If the file named is inside wp-admin or wp-includes, something modified it, which is also worth a malware check. With WP-CLI:
wp core verify-checksums
wp core download --force --skip-content
The second command refreshes core files without touching wp-content or wp-config.php.
8. Last resort: output buffering
You can mask the problem by buffering output, for example adding ob_start(); at the top of wp-config.php. It works because PHP holds the body back until the script ends. But it hides the real bug, so treat it as a temporary bandage while you track down the culprit, not a fix.
Prevention
- Edit code with a proper editor set to UTF-8 without BOM. Avoid Word or Notepad for PHP.
- Skip the closing
?>in pure-PHP files. - Test snippets on a staging copy first. A pasted snippet with a stray space is the most common cause we see.
- Keep
WP_DEBUG_DISPLAYoff on live sites and log to a file instead:
define('WP_DEBUG', true);
define('WP_DEBUG_LOG', true);
define('WP_DEBUG_DISPLAY', false);
- Take a cPanel backup before editing
wp-config.phporfunctions.php.
Still stuck after the steps above? Open a ticket with SkyServer support and include the full warning text. The file path and line number in it let us pinpoint the problem quickly.
Frequently Asked Questions
Why does the error name a file I never edited?
Because the warning is raised where header() or setcookie() was called. The real culprit is the file in the "output started at" part. Always trust that path.
Is this error dangerous for my site's security?
The error itself isn't a security hole, but displaying file paths publicly isn't ideal. Turn off WP_DEBUG_DISPLAY. If the culprit is a core file you didn't change, scan for malware.
I only see it in wp-admin, not on the front end. Why?
Admin pages set cookies and redirect more often, so they trip the problem first. A plugin that loads only in the dashboard may be the source.
Can I just hide the warning with error_reporting(0)?
That hides the text, but the headers still fail, so logins and redirects can stay broken. Fix the output source instead.
